All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.clickzetta.client.jdbc.core.CZStatement Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
package com.clickzetta.client.jdbc.core;

import java.sql.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.clickzetta.client.ClickZettaClient;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.util.JsonFormat;
import cz.proto.FileSystem;
import cz.proto.ObjectIdentifier;
import cz.proto.ObjectType;
import cz.proto.coordinator.CoordinatorServiceOuterClass;
import cz.proto.coordinator.CoordinatorServiceOuterClass.SubmitJobResponse;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.lang.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.clickzetta.client.jdbc.core.CZLakehouseResponseHelper.genVolumeResultSet;

public class CZStatement implements Statement {
  public static final String JOB_ALREADY_EXIST = "CZLH-60007";
  public static final String JOB_NOT_EXIST = "CZLH-60005";
  public static final String JOB_STATUS_UNKNOWN = "CZLH-60022";
  public static final String JOB_NOT_SUBMITTED = "CZLH-60023";
  private final CZConnectContext connectContext;
  private final LakehouseClient lakehouseClient;
  private String warning;
  private String errorMessage;
  private boolean cancelled = false;

  // private boolean created = false;
  private CoordinatorServiceOuterClass.JobStatus jobStatus;
  Logger logger = LoggerFactory.getLogger(CZStatement.class);

  private final int MAX_TRIED = 10;
  private final int INTERVAL = 6000;
  private CZJobMetric jobMetric;
  enum CommandType {
    NO_COPY, COPY_FROM, COPY_TO, PUT_VOLUME, GET_VOLUME
  }
  public CommandType commandType = CommandType.NO_COPY;
  public String copyTable;
  public String copyLocation;
  private Integer queryTimeoutMs = null;
  private int executeCount = 0;

  public CZStatement(CZConnectContext context) {
    this.connectContext = context;
    warning = "";
    lakehouseClient = context.lakehouseClient();
    jobMetric = new CZJobMetric();
    initCommandMap();
    logger.info("Statement {} init done", this);
  }

  public ResultSet executeQuery(String sql, String jobId) throws Exception {
    this.jobIdPassIn = jobId;
    ResultSet rs = executeQuery(sql);
    logger.info("Job {} executeQuery done, got resultSet: {}", jobId, rs);
    return rs;
  }

  @Override
  public ResultSet executeQuery(String sql) throws SQLException {
    executeInternal(sql);
    if (resultSet == null) {
      throw new CZNullResultException(String.format("Job %s: %s resultSet is null.", jobId, sql));
    }
    logger.info("Job {} executeQuery done, got resultSet: {}", jobId, resultSet);
    return resultSet;
  }

  /**
   * @return true if job finish, else false
   */
  public boolean isJobFinish() throws Exception {
    return isJobFinish(jobId);
  }

  public boolean isJobFinish(String jobId) throws Exception {
    if (resultSet != null) {
      return true;
    } else if (!cancelled && !jobId.isEmpty()) {
      CoordinatorServiceOuterClass.GetJobResultResponse getJobResultResponse =
          getJobResult(jobId, connectContext.getWorkspace(), connectContext.getInstanceId(),
              connectContext.useInternalOssEndpoint(), 1, INTERVAL);
      jobStatus = getJobResultResponse.getStatus();
      return isJobFinish(getJobResultResponse, 10);
    }
    throw new CZException(String.format("Get job %s status, %s.", jobId,
        cancelled ? "job already cancelled" : "error"));
  }

  private boolean executeInternal(String s) throws SQLException {
    logger.debug("executeInternal: {}", s);
    long beginExecute = System.currentTimeMillis();
    long startExecute = beginExecute;

    cancelled = false;
    if (resultSet != null) {
      resultSet.close();
      resultSet = null;
    }
    jobResultSet = null;
    commandType = CommandType.NO_COPY;
    copyTable = "";
    copyLocation = "";
    warning = "";
    executeCount++;
    List multiQueries = CZUtil.querySplit2(s, ';');

    int queryIndex = 0;
    for (; queryIndex < multiQueries.size(); queryIndex++) {
      String trimed = multiQueries.get(queryIndex).trim();
      if (!trimed.isEmpty()) {
        if (!executeManageQuery(trimed)) {
          break;
        } else {
          connectContext.addConfigStatementHistory(trimed);
        }
      }
    }

    String logStr = String.format("Parse manage query %s: %d(ms)\n", jobId, System.currentTimeMillis() - beginExecute);

    beginExecute = System.currentTimeMillis();
    logger.debug(logStr);
    if (connectContext.showDebug()) {
      warning += logStr;
    }

    if (queryIndex == multiQueries.size()) {
      return resultSet != null;
    }
    String sql = multiQueries.get(queryIndex);
    // replace select {fn ... }
    // sql = CZUtil.fnReplace(sql);

    String[] volumeInfo = new String[2];
    connectContext.setConfig("cz.sql.sdk.info", ClickZettaClient.SDK_INFO);
    if (commandType == CommandType.COPY_TO) {
      if (copyTable.contains(" ")) {
        sql = copyTable;
      } else {
        sql = "select * from " + copyTable;
      }
      // TODO: do we need to set csv related (orginally for upload only) context here?
      connectContext.setConfig("cz.sql.adhoc.result.type", "file");
      if (!connectContext.getConfigs().containsKey("cz.sql.adhoc.default.format") ||
            (connectContext.getConfigs().containsKey("cz.sql.adhoc.default.format") &&
              connectContext.getConfigs().get("cz.sql.adhoc.default.format").equalsIgnoreCase("ARROW"))) {
        connectContext.setConfig("cz.sql.adhoc.default.format", "TEXT");
      }
      if (!connectContext.getConfigs().containsKey("cz.sql.job.result.file.presigned.url.enabled")) {
        connectContext.setConfig("cz.sql.job.result.file.presigned.url.enabled", "true");
      }
      if (!connectContext.getConfigs().containsKey("cz.sql.job.result.file.presigned.url.ttl")) {
        connectContext.setConfig("cz.sql.job.result.file.presigned.url.ttl", "3600");
      }
      logger.info("copy to command {}", sql);
    } else if (commandType == CommandType.COPY_FROM) {
      logger.info("copy from command {}", sql);
      return true;
    } else if (commandType == CommandType.PUT_VOLUME) {
      volumeInfo = connectContext.getVolumePath().split("/", 2);
      sql = "select get_presigned_url(volume " +
              volumeInfo[0] +
              ", '" +
              volumeInfo[1] +
              "', " +
              3600 +
              ", 'PUT'" +
              ")";
      logger.info("put volume command {}", sql);
    } else if (commandType == CommandType.GET_VOLUME) {
      volumeInfo = connectContext.getVolumePath().split("/", 2);
      sql = "select get_presigned_url(volume " +
              volumeInfo[0] +
              ", '" +
              volumeInfo[1] +
              "', " +
              3600 +
              ", 'GET'" +
              ")";
      logger.info("get volume command {}", sql);
    }

    logStr = String.format("Before execute %s: %d(ms)\n", jobId, System.currentTimeMillis() - beginExecute);
    beginExecute = System.currentTimeMillis();
    logger.debug(logStr);
    if (connectContext.showDebug()) {
      warning += logStr;
    }

    jobStatus = CoordinatorServiceOuterClass.JobStatus.newBuilder().build();
    try {
      sql = sql + "\n;";
      if (!jobIdPassIn.isEmpty()) {
        jobId = jobIdPassIn;
      } else {
        jobId = CZRequestIdGenerator.getInstance().generate();
      }
      logStr = String.format("Gen job id %s: %s %d(ms)\n", jobId, sql, System.currentTimeMillis() - beginExecute);
      beginExecute = System.currentTimeMillis();
      logger.info(logStr);
      if (connectContext.showDebug()) {
        warning += logStr;
        warning += String.format("\nConfigInfo maxRows: %d, %d\n\n", getMaxRows(), connectContext.getMaxRowSize());
      }

      String requestStr = CZLakehouseRequestHelper.constructSubmitJobRequest(
              sql, jobId, connectContext.getConfigStatementHistory(), connectContext.toJson(),
              connectContext.getWorkspace(),
              connectContext.getSchema(),
              connectContext.getVirtualCluster(),
              CoordinatorServiceOuterClass.JobRequestMode.HYBRID,
              connectContext.getHybridPollingTimeout(),
              connectContext.getPriority(),
              connectContext.getConfigs(),
              connectContext.getQueryTag(),
              connectContext.getHost(),
              queryTimeoutMs == null ? connectContext.getJdbcQueryTimeoutMs() : queryTimeoutMs);
      logStr = String.format("Send request: %s, %s, %s, %d(ms)\n", jobId, sql, requestStr,
              System.currentTimeMillis() - beginExecute);
      logger.debug(logStr);
      if (connectContext.showDebug()) {
        warning += logStr;
      }

      // submit job
      SubmitJobResponse response = submitJob(requestStr);

      // When the error code is CZLH-57015, jobid needs to be generated to resubmit the job, at most 1 times
      if (response != null && isNeedReExecute(response.getStatus().getErrorCode())) {
          return executeInternal(s);
      }

      if (commandType == CommandType.NO_COPY) {
        if (CZConnectContext.getDbapiMode() && sql.toLowerCase().startsWith("show tables")) {
          // hack for dbapi mode
          resultSet = getTableNameResultSet(resultSet, jobResultSet);
        }
      } else if (commandType == CommandType.COPY_TO) {
        beginExecute = System.currentTimeMillis();
        connectContext.setConfig("cz.sql.adhoc.result.type", "embedded");
        connectContext.setConfig("cz.sql.adhoc.default.format", "ARROW");
        CZUtil.saveRemoteFileToLocal(jobResultSet.getLocation(), copyLocation);
        logStr = String.format("Save oss file to local: %s, %d(ms)\n", jobId, System.currentTimeMillis() - beginExecute);
        logger.info(logStr);
        if (connectContext.showDebug()) {
          warning += logStr;
        }
        return false;
      } else if (commandType == CommandType.PUT_VOLUME) {
        while (resultSet.next()) {
          CZUtil.putObjectWithHttp((String) resultSet.getObject(1), connectContext.getLocalFilePath());
        }
        resultSet = genVolumeResultSet(connectContext.getLocalFilePath(), volumeInfo[1], commandType);
      } else if (commandType == CommandType.GET_VOLUME && connectContext.getVolumeDownloadFlag()) {
        while (resultSet.next()) {
          CZUtil.getObjectWithHttp((String) resultSet.getObject(1), connectContext.getLocalFilePath());
        }
        resultSet = genVolumeResultSet(volumeInfo[1], connectContext.getLocalFilePath(), commandType);
      }
      return resultSet != null;
    } catch (CZUnsupportedVersionException | SQLTimeoutException | CZJobAsyncRunningException e) {
      throw e;
    } catch (Exception e) {
      logger.error("executeQuery fail", e);
      errorMessage = e.getMessage();
      warning += String.format("Job %s, status %s, \n%s", jobId, jobStatus.getState().isEmpty() ? "NoState" : jobStatus.getState(), e.getMessage());
    } finally {
      executeCount--;
    }
    throw new SQLException(warning);
  }

  private ResultSet getTableNameResultSet(ResultSet resultSet, CoordinatorServiceOuterClass.JobResultSet jobResult)
          throws SQLException {
    List rawDatas = new ArrayList<>();
    while (resultSet.next()) {
      rawDatas.add((String) resultSet.getObject(2));
    }
    List columnMetaDatas = new ArrayList<>();
    columnMetaDatas.add(CZLakehouseResponseHelper.parseColumnMetaData(jobResult.getMetadata()).get(1));
    CZResult czResult = new CZInMemoryTextResult(rawDatas, columnMetaDatas, connectContext.getMaxRowSize());
    return new CZTextResultSet(columnMetaDatas, czResult);
  }

  // return true if submitted, else false
  private boolean checkNotSubmitted() {
    CoordinatorServiceOuterClass.GetJobResultResponse getJobResultResponse = null;
    try {
      getJobResultResponse = getJobResult(jobId, connectContext.getWorkspace(), connectContext.getInstanceId(),
              connectContext.useInternalOssEndpoint(), 3, 3000);
    } catch (Exception e) {
      return true;
    }
    return getJobResultResponse == null;
  }

  private boolean waitContinueSubmit(int tried) {
    if (tried >= MAX_TRIED) {
      return false;
    }
    if (checkNotSubmitted()) {
      try {
        Thread.sleep(500L * tried);
        return true;
      } catch (InterruptedException e) {
        logger.warn("Receive Interrupt but Job {} is still running...", jobId);
      }
    }
    return false;
  }

  private SubmitJobResponse submitJob(String requestStr) throws Exception {
    long beginExecute = System.currentTimeMillis();
    long gatewayBegin = 0;
    jobMetric.setClientRequestMs(beginExecute);
    SubmitJobResponse response = null;
    for (int tried = 1; !cancelled && tried <= MAX_TRIED; ++tried) {
      try {
        response = lakehouseClient.submitJob(requestStr, connectContext, jobId);
        if (gatewayBegin == 0) {
          gatewayBegin = connectContext.getHttpHelper().getGatewayStartMs();
        }
        if (response == null) {
          if (waitContinueSubmit(tried)) {
            continue;
          }
        } else if (stsTokenError(response.getResultSet(), connectContext.useInternalOssEndpoint())) {
          throw new SQLException("Sts token is not generated");
        } else if (response.getStatus().getErrorCode().equals(JOB_ALREADY_EXIST)) {
          logger.warn("Job {} already exist, no need to submit again", jobId);
        } else if (response.getStatus().getErrorCode().equals(JOB_STATUS_UNKNOWN)) {
          logger.warn("Job {} status unknown, need to submit again", jobId);
          if (waitContinueSubmit(tried)) {
            continue;
          }
        } else if (response.getStatus().getErrorCode().equals(JOB_NOT_SUBMITTED)) {
          logger.warn("Job {} not submitted, need to submit again", jobId);
          if (waitContinueSubmit(tried)) {
            continue;
          }
        }
      } catch (NumberFormatException e) {
        throw e;
      } catch (Exception e) {
        long cost = System.currentTimeMillis() - beginExecute;
        String logStr = String.format("submitJob throw Exception, %s tried %d, left %d, got exception: %s, cost: %d(ms)\n",
                jobId, tried, MAX_TRIED-tried, e, cost);
        warning += logStr;
        if (waitContinueSubmit(tried)) {
          logger.warn(logStr);
          continue;
        } else if (tried < MAX_TRIED) {
          // job submitted success
          break;
        }
        logger.error(logStr);
        throw e;
      }
      if (response == null || !CZUtil.isJobFinished(response.getRespStatus(), response.getStatus(), response.getResultSet(), 0)) {
        try {
          StringBuilder warnInLoop = retryGetResult(beginExecute);
          warning += warnInLoop.toString();
          break;
        } catch (CZJobNotExistsException e) {
          // do nothing, try submitJob again
          logger.error("job {} not exist, try submit again", jobId, e);
        }
      } else {
        if (isNeedReExecute(response.getStatus().getErrorCode())) {
          break;
        }
        resultSet = CZLakehouseResponseHelper.parseSubmitJobResponse(response,
                connectContext.getMaxRowSize(),
                connectContext.useInternalOssEndpoint(),
                connectContext.getConfigs(),
                connectContext.useObjectStoreHttps());
        jobStatus = response.getStatus();
        jobResultSet = response.getResultSet();
        String logStr = String.format("Parse result finish: %s, %d(ms), lakehouse running ms: %d\n", jobId,
                System.currentTimeMillis() - beginExecute,
                response.getStatus().getRunningTime());
        logger.info(logStr);
        warning = connectContext.showDebug() ? warning += logStr : warning;
        break;
      }
    }

    jobMetric.setGatewayStartMs(gatewayBegin);
    jobMetric.setGatewayEndMs(connectContext.getHttpHelper().getGatewayEndMs());
    jobMetric.setServerSubmitMs(jobStatus.getSubmitTime());
    jobMetric.setServerStartMs(jobStatus.getStartTime());
    jobMetric.setServerEndMs(jobStatus.getEndTime());
    jobMetric.fillServerJobProfiling(jobStatus.getJobProfiling());
    jobMetric.setClientResponseMs(connectContext.getHttpHelper().getHttpCallEndMs());
    return response;
  }

  private boolean isNeedReExecute(String errorCode) {
    return errorCode.equals("CZLH-57015") && executeCount < 2;
  }

  /**
   * getResult loop
   */
  private StringBuilder retryGetResult(long startExecute) throws Exception {
    int retryTime = 1, sleepTimer = 50;
    StringBuilder warnInLoop = new StringBuilder();
    while (true) {
      CoordinatorServiceOuterClass.GetJobResultResponse getJobResultResponse =
              getJobResult(jobId, connectContext.getWorkspace(), connectContext.getInstanceId(),
                           connectContext.useInternalOssEndpoint(), 1, INTERVAL);
      if (getJobResultResponse == null) {
        warnInLoop.append("Get Job Result Failed. Retry for ").append(retryTime).append(" times\n");
        retryTime++;
        continue;
      }
      jobStatus = getJobResultResponse.getStatus();
      if (isJobFinish(getJobResultResponse, retryTime)) {
        if (connectContext.showDebug()) {
          warnInLoop.append("JobResult: \n").append(responseToString(getJobResultResponse)).append("\n\n\n");
        }
        break;
      }
      long timeExecuted = System.currentTimeMillis() - startExecute;
      String logStr = String.format("Job %s is running... status: %s, tried %d, %d(ms)\n", jobId, jobStatus.toString(),
          retryTime, timeExecuted);
      logger.info(logStr);
      retryTime++;
      if (connectContext.getQueryTimeout() > 0 && timeExecuted > connectContext.getQueryTimeout()) {
        logger.error("Job {} execute timeout, used {} ms", jobId, timeExecuted);
        cancel();
        throw new SQLTimeoutException(String.format("executeQuery timeout, used %d ms", timeExecuted));
      } else if (connectContext.getJDBCMode() > 0 && timeExecuted > 30*1000) {
        logger.warn("Job {} used {} ms, throw async running exception.", jobId, timeExecuted);
        throw new CZJobAsyncRunningException(jobId);
      }
      try {
        Thread.sleep(sleepTimer);
      } catch (InterruptedException e) {
        System.out.println("Job " + jobId + " is still running...");
        logger.warn("Receive Interrupt but Job {} is still running...", jobId);
      }
      if (sleepTimer < 3000) {
        sleepTimer *= 2;
      }
    }
    return warnInLoop;
  }

  private boolean isJobFinish(CoordinatorServiceOuterClass.GetJobResultResponse getJobResultResponse,
                              int retryTime) throws Exception {
    if (CZUtil.isJobFinished(getJobResultResponse.getRespStatus(), getJobResultResponse.getStatus(),
        getJobResultResponse.getResultSet(), retryTime)) {
      resultSet = CZLakehouseResponseHelper.parseJobResult(
          getJobResultResponse.getStatus(),
          getJobResultResponse.getResultSet(),
          connectContext.getMaxRowSize(),
          connectContext.useInternalOssEndpoint(),
          connectContext.getConfigs(),
          connectContext.useObjectStoreHttps());
      jobResultSet = getJobResultResponse.getResultSet();
      return true;
    }
    return false;
  }

  private String responseToString(SubmitJobResponse response) throws InvalidProtocolBufferException {
    SubmitJobResponse.Builder logResponse = SubmitJobResponse.newBuilder(response);
    logResponse.getResultSetBuilder().clearLocation().clearData();
    return JsonFormat.printer().print(logResponse);
  }

  private String responseToString(CoordinatorServiceOuterClass.GetJobResultResponse response) throws InvalidProtocolBufferException {
    CoordinatorServiceOuterClass.GetJobResultResponse.Builder logResponse = CoordinatorServiceOuterClass.GetJobResultResponse.newBuilder(response);
    logResponse.getResultSetBuilder().clearLocation().clearData();
    return JsonFormat.printer().print(logResponse);
  }

  @Override
  public int executeUpdate(String s) throws SQLException {
    return 0;
  }

  @Override
  public void close() throws SQLException {
    logger.info("Statement: {} start closing resultSet: {}", this, resultSet);
    if (resultSet != null) {
      resultSet.close();
      resultSet = null;
    }
  }

  @Override
  public int getMaxFieldSize() throws SQLException {
    return 0;
  }

  @Override
  public void setMaxFieldSize(int i) throws SQLException {

  }

  @Override
  public int getMaxRows() throws SQLException {
    return maxRows;
  }

  @Override
  public void setMaxRows(int max) throws SQLException {
    if (max > 1000000) {
      throw new SQLException("setMaxRows larger than 1000000.");
    }
    this.maxRows = max;
    connectContext.setMaxRowSize(max);
  }

  @Override
  public void setEscapeProcessing(boolean b) throws SQLException {

  }

  @Override
  public int getQueryTimeout() throws SQLException {
    if (queryTimeoutMs == null) {
      return connectContext.getJdbcQueryTimeoutMs() / 1000;
    }
    return queryTimeoutMs / 1000;
  }

  @Override
  public void setQueryTimeout(int i) throws SQLException {
    queryTimeoutMs = i * 1000;
  }

  @Override
  public void cancel() throws SQLException {
    cancel(jobId);
  }

  public void cancel(String jobId) throws SQLException {
    logger.info("try to cancel query: {}", jobId);
    try {
      String requestStr = CZLakehouseRequestHelper.constructCancelJobRequest(jobId
              , connectContext.getWorkspace(), -1);
      CoordinatorServiceOuterClass.CancelJobResponse response =
              lakehouseClient.cancelJob(requestStr, connectContext, jobId);
      logger.info("cancel response {}", JsonFormat.printer().print(response));
      if (jobId.equals(this.jobId)) {
        cancelled = true;
      }
    } catch (Exception e) {
      logger.info("cancel fail", e);
    }
  }

  @Override
  public SQLWarning getWarnings() throws SQLException {
    if (!warning.isEmpty()) {
      return new SQLWarning(jobId + ", " + warning);
    } else {
      return null;
    }
  }

  @Override
  public void clearWarnings() throws SQLException {
    warning = "";
  }

  @Override
  public void setCursorName(String s) throws SQLException {

  }

  public CoordinatorServiceOuterClass.JobStatus getJobStatus() {
    return jobStatus;
  }

  boolean executeManageQuery(String originStr) throws SQLException {
    String stripOriginStr = CZUtil.stripLeadingComment(originStr);
    String str = stripOriginStr.toLowerCase();
    if (!str.startsWith("use") && !str.startsWith("set") && !str.startsWith("clear")
            && !str.startsWith("show configs") && !str.startsWith("show context") && !str.startsWith(
            "print") && !str.startsWith("copy") && !str.startsWith("put") && !str.startsWith("get volume")) {
      return false;
    }
    for (Map.Entry> entry : commandMap.entrySet()) {
      if (str.startsWith(entry.getKey())) {
          return entry.getValue().apply(stripOriginStr);
      }
    }
    return false;
  }

  public CoordinatorServiceOuterClass.JobResultSet getJobResultSet() {
    return jobResultSet;
  }

  public String getErrorMessage() {
    return errorMessage;
  }

  public boolean execute(String statement, String jobId) throws Exception {
    this.jobIdPassIn = jobId;
    return execute(statement);
  }

  @Override
  public boolean execute(String s) throws SQLException {
    return executeInternal(s);
  }

  @Override
  public ResultSet getResultSet() throws SQLException {
    logger.info("Statement {} getResultSet: {}", this, resultSet);
    return resultSet;
  }

  @Override
  public int getUpdateCount() throws SQLException {
    return -1;
  }

  @Override
  public boolean getMoreResults() throws SQLException {
    return false;
  }

  @Override
  public void setFetchDirection(int i) throws SQLException {

  }

  @Override
  public int getFetchDirection() throws SQLException {
    return 0;
  }

  @Override
  public void setFetchSize(int i) throws SQLException {

  }

  @Override
  public int getFetchSize() throws SQLException {
    return 0;
  }

  @Override
  public int getResultSetConcurrency() throws SQLException {
    return 0;
  }

  @Override
  public int getResultSetType() throws SQLException {
    return 0;
  }

  @Override
  public void addBatch(String s) throws SQLException {

  }

  @Override
  public void clearBatch() throws SQLException {

  }

  @Override
  public int[] executeBatch() throws SQLException {
    return new int[0];
  }

  @Override
  public Connection getConnection() throws SQLException {
    return null;
  }

  @Override
  public boolean getMoreResults(int i) throws SQLException {
    return false;
  }

  @Override
  public ResultSet getGeneratedKeys() throws SQLException {
    return null;
  }

  @Override
  public int executeUpdate(String s, int i) throws SQLException {
    return 0;
  }

  @Override
  public int executeUpdate(String s, int[] ints) throws SQLException {
    return 0;
  }

  @Override
  public int executeUpdate(String s, String[] strings) throws SQLException {
    return 0;
  }

  @Override
  public boolean execute(String s, int i) throws SQLException {
    return false;
  }

  @Override
  public boolean execute(String s, int[] ints) throws SQLException {
    return false;
  }

  @Override
  public boolean execute(String s, String[] strings) throws SQLException {
    return false;
  }

  @Override
  public int getResultSetHoldability() throws SQLException {
    return 0;
  }

  @Override
  public boolean isClosed() throws SQLException {
    return false;
  }

  @Override
  public void setPoolable(boolean poolable) throws SQLException {
    this.poolable = poolable;
  }

  @Override
  public boolean isPoolable() throws SQLException {
    return poolable;
  }

  @Override
  public void closeOnCompletion() throws SQLException {

  }

  @Override
  public boolean isCloseOnCompletion() throws SQLException {
    return false;
  }

  @Override
  public  T unwrap(Class aClass) throws SQLException {
    return null;
  }

  @Override
  public boolean isWrapperFor(Class aClass) throws SQLException {
    return false;
  }

  public CoordinatorServiceOuterClass.GetJobPlanResponse getJobPlan(CoordinatorServiceOuterClass.JobID jobId) throws Exception {
    String request =
            CZLakehouseRequestHelper.constructGetJobPlanRequest(jobId.getId()
                    , jobId.getWorkspace(), jobId.getInstanceId());
    return lakehouseClient.getJobPlan(request, connectContext, jobId.getId());
  }

  @Deprecated
  public CoordinatorServiceOuterClass.GetJobSummaryResponse getJobSummary(CoordinatorServiceOuterClass.JobID jobId) throws Exception {
    String request = CZLakehouseRequestHelper.constructGetJobSummaryRequest(jobId.getId()
            , jobId.getWorkspace(), jobId.getInstanceId());
    return lakehouseClient.getJobSummary(request, connectContext, jobId.getId());
  }

  @Deprecated
  public CoordinatorServiceOuterClass.GetJobProgressResponse getJobProgress(CoordinatorServiceOuterClass.JobID jobId) throws Exception {
    String request = CZLakehouseRequestHelper.constructGetJobProgressRequest(jobId.getId()
            , jobId.getWorkspace(), jobId.getInstanceId());
    return lakehouseClient.getJobProgress(request, connectContext, jobId.getId());
  }

  private CoordinatorServiceOuterClass.GetJobResultResponse getJobResult(CoordinatorServiceOuterClass.JobID jobId,
                                                                        boolean useInternalOss,
                                                                        int max_retry, int interval) throws Exception {
    return getJobResult(jobId.getId(), jobId.getWorkspace(), jobId.getInstanceId(), useInternalOss, max_retry, interval);
  }

  private CoordinatorServiceOuterClass.GetJobResultResponse getJobResult(String jobId, String workspace, long instanceId,
                                                                         boolean useInternalOss,
                                                                         int max_retry, int interval) throws Exception {
    String request = CZLakehouseRequestHelper.constructGetJobResultRequest(jobId, workspace, instanceId, connectContext.getHost());
    CoordinatorServiceOuterClass.GetJobResultResponse getResult = null;
    for (int tried = 1; tried <= max_retry; ++tried) {
      try {
        getResult = lakehouseClient.getJobResult(request, connectContext, jobId);
        // sts token error, try again
        if (getResult == null) {
          continue;
        } else if (stsTokenError(getResult.getResultSet(), useInternalOss)) {
          logger.warn(String.format("Oss sts token is not given, tried %d.", tried));
          if (!cancelled && tried < max_retry) {
            Thread.sleep(interval);
            continue;
          } else {
            throw new SQLException("Sts token is not generated");
          }
        }
        break;
      } catch (Exception e) {
        logger.error("getJobResult error: {}", e.toString());
        if (!e.getMessage().contains("NoCoordinatorServing") && !e.getMessage().contains(JOB_STATUS_UNKNOWN) &&
                !e.getMessage().contains(JOB_NOT_SUBMITTED)) {
          throw e;
        } else {
          Thread.sleep(interval);
        }
      }
    }
    // job not exist
    if (getResult != null && getResult.getStatus().getErrorCode().equals(JOB_NOT_EXIST)) {
      throw new CZJobNotExistsException(jobId);
    }
    // http request error
    if (getResult != null && getResult.getStatus().getState().isEmpty()) {
      throw new SQLException(JsonFormat.printer().print(getResult));
    }
    return getResult;
  }

  private boolean stsTokenError(CoordinatorServiceOuterClass.JobResultSet resultSet, boolean useInternalOss) {
    if (!resultSet.hasData() && resultSet.hasLocation() && resultSet.getLocation().getLocationCount() > 0) {
      CoordinatorServiceOuterClass.JobResultLocation location = resultSet.getLocation();
      if (location.getFileSystem().equals(FileSystem.FileSystemType.GCS)) {
        return location.getStsToken().isEmpty();
      } else {
        return location.getStsAkId().isEmpty() || location.getStsAkSecret().isEmpty() ||
                location.getStsToken().isEmpty();
      }
    }
    return false;
  }

  public CZJobMetric getJobMetric() {
    return jobMetric;
  }

  @Deprecated
  public CoordinatorServiceOuterClass.GetJobProfileResponse getJobProfile(CoordinatorServiceOuterClass.JobID jobId) throws Exception {
    String request = CZLakehouseRequestHelper.constructGetJobProfileRequest(jobId.getId()
            , jobId.getWorkspace(), jobId.getInstanceId());
    return lakehouseClient.getJobProfile(request, connectContext, jobId.getId());
  }

  @Deprecated
  public CoordinatorServiceOuterClass.CreateWorkspaceResponse createWorkspace(CoordinatorServiceOuterClass.CreateWorkspaceRequest request) throws Exception {
    return lakehouseClient.createWorkspace(JsonFormat.printer().print(request), connectContext);
  }

  @Deprecated
  public CoordinatorServiceOuterClass.DeleteWorkspaceResponse deleteWorkspace(String workspaceName, long instanceId) throws Exception {
    CoordinatorServiceOuterClass.DeleteWorkspaceRequest req =
            CoordinatorServiceOuterClass.DeleteWorkspaceRequest.newBuilder().setIdentifier(
                    ObjectIdentifier.newBuilder().setName(workspaceName).setInstanceId(instanceId).setType(ObjectType.WORKSPACE).build()).build();
    return lakehouseClient.deleteWorkspace(JsonFormat.printer().print(req), connectContext);
  }

  @Deprecated
  public CoordinatorServiceOuterClass.UpdateWorkspaceResponse updateWorkspace(CoordinatorServiceOuterClass.UpdateWorkspaceRequest request) throws Exception {
    return lakehouseClient.updateWorkspace(JsonFormat.printer().print(request), connectContext);
  }

  @Deprecated
  public CoordinatorServiceOuterClass.GetWorkspaceResponse getWorkspace(String workspaceName, long instanceId) throws Exception {
    CoordinatorServiceOuterClass.GetWorkspaceRequest req =
            CoordinatorServiceOuterClass.GetWorkspaceRequest.newBuilder().setIdentifier(
                    ObjectIdentifier.newBuilder().setName(workspaceName).setInstanceId(instanceId).setType(ObjectType.WORKSPACE).build()).build();
    return lakehouseClient.getWorkspace(JsonFormat.printer().print(req), connectContext);
  }

  @Deprecated
  public CoordinatorServiceOuterClass.ListWorkspacesResponse listWorkspaces(long instanceId) throws Exception {
    CoordinatorServiceOuterClass.ListWorkspacesRequest req =
            CoordinatorServiceOuterClass.ListWorkspacesRequest.newBuilder().setInstanceId(instanceId).build();
    return lakehouseClient.listWorkspaces(JsonFormat.printer().print(req), connectContext);
  }

  public CoordinatorServiceOuterClass.OpenTableResponse openTable(
      String schemaName, String tableName, boolean icebergFormat) throws Exception {
    String request = CZLakehouseRequestHelper.constructOpenTableRequest(
        connectContext.getWorkspace()
        , schemaName, tableName, connectContext.getInstanceId(), icebergFormat);
    return lakehouseClient
        .openTable(request, connectContext, schemaName, tableName);
  }

  private String parseStrInQuote(String str) throws SQLException {
    if (str.length() < 2) {
      throw new SQLException(str + " should start and end with ' or \"");
    } else if (str.charAt(0) == '\'' && str.charAt(str.length() - 1) == '\'') {
      return str.substring(1, str.length() - 1);
    } else if (str.charAt(0) == '"' && str.charAt(str.length() - 1) == '"') {
      return str.substring(1, str.length() - 1);
    } else {
      throw new SQLException(str + " should start and end with ' or \"");
    }
  }

  @FunctionalInterface
  interface ThrowingFunction {
    R apply(T t) throws E;
  }

  private final Map>commandMap = new LinkedHashMap<>();

  private int maxRows = 0;
  private boolean poolable = false;

  private final Set openResultSets = ConcurrentHashMap.newKeySet();

  // result set currently in use
  private ResultSet resultSet = null;
  private CoordinatorServiceOuterClass.JobResultSet jobResultSet = null;
  private String jobId = "";
  private String jobIdPassIn = "";

  private Boolean isClosed = false;
  private final String useSchema_ = "use schema ";
  private final String use_ = "use ";
  private final String useVcluster_ = "use vcluster ";
  private final String setMode = "set mode";
  private final String setPriority = "set priority";
  private final String setScheduleJobPriority = "set schedule_job_priority";
  private final String setConfig_ = "set ";
  private final String showConfig = "show configs";
  private final String showContext = "show context";
  private final String clearConfig = "clear configs";
  private final String printDebug = "print debug";
  private final String printNoDebug = "print nodebug";
  private final String clearContext = "clear context";
  private final String setWorkspace = "set workspace";
  private final String setInstanceId = "set instanceid";
  private final String setUserId = "set userid";
  private final String setPollingTimeout = "set pollingtimeout";
  private final String setQueryTimeout = "set querytimeout";
  private final String setJdbcQueryTimeout = "set jdbc.query.timeout.ms";
  private final String setMaxRowSize = "set maxrowsize";
  private final String setCopyCsvDelimiter = "set copy.csv.delimiter";
  private final String setCopyCsvFieldDelimiter = "set copy.csv.field.delimiter";
  private final String setCopyCsvEscape = "set copy.csv.escape";
  private final String setCopyCsvWithHeader = "set copy.csv.with.header";
  private final String setCopyCsvSkipHeader = "set copy.csv.skip.header";
  private final String setCopyCsvNullString = "set copy.csv.null.string";
  private final String setCopyCsvIgnoreMalformedRecord = "set copy.csv.ignore.malformed.record";
  private final String setCopyCsvTimeZonePlus = "set copy.csv.timezone.plus";
  private final String copy_ = "copy ";
  private final String setQueryTag = "set query_tag";
  private final String putvolume = "put ";
  private final String getVolume = "get volume";
  private final String setVolumeDownload = "set volume.download";

  private void initCommandMap() {
    commandMap.put(clearContext, this::clearContext);
    commandMap.put(useSchema_, this::useSchema);
    commandMap.put(useVcluster_, this::useVCluster);
    commandMap.put(use_, this::useSchemaAlias);
    commandMap.put(setUserId, this::setUserId);
    commandMap.put(setInstanceId, this::setInstanceId);
    commandMap.put(setWorkspace, this::setWorkspace);
    commandMap.put(setMode, this::setMode);
    commandMap.put(setPollingTimeout, this::setPollingTimeout);
    commandMap.put(setQueryTimeout, this::setQueryTimeout);
    commandMap.put(setJdbcQueryTimeout, this::setJdbcQueryTimeout);
    commandMap.put(setMaxRowSize, this::setMaxRowSize);
    commandMap.put(setQueryTag, this::setQueryTag);
    commandMap.put(setCopyCsvDelimiter, this::setCopyCsvDelimiter);
    commandMap.put(setCopyCsvFieldDelimiter, this::setCopyCsvFieldDelimiter);
    commandMap.put(setCopyCsvEscape, this::setCopyCsvEscape);
    commandMap.put(setCopyCsvWithHeader, this::setCopyCsvWithHeader);
    commandMap.put(setCopyCsvSkipHeader, this::setCopyCsvSkipHeader);
    commandMap.put(setCopyCsvNullString, this::setCopyCsvNullString);
    commandMap.put(setCopyCsvIgnoreMalformedRecord, this::setCopyCsvIgnoreMalformedRecord);
    commandMap.put(setCopyCsvTimeZonePlus, this::setCopyCsvTimeZonePlus);
    commandMap.put(setPriority, this::setPriority);
    commandMap.put(setScheduleJobPriority, this::setScheduleJobPriority);
    commandMap.put(setVolumeDownload, this::setVolumeDownload);
    commandMap.put(setConfig_, this::setConfig);
    commandMap.put(showConfig, this::showConfig);
    commandMap.put(showContext, this::showContext);
    commandMap.put(clearConfig, this::clearConfig);
    commandMap.put(printDebug, this::printDebug);
    commandMap.put(printNoDebug, this::printNoDebug);
    commandMap.put(copy_, this::copyCommand);
    commandMap.put(putvolume, this::putVolume);
    commandMap.put(getVolume, this::getVolume);
  }

  private boolean clearContext(String stripOriginStr) throws SQLException {
    logger.info("clearing context");
    connectContext.clearContextForDirectMode();
    return true;
  }

  private boolean useSchema(String stripOriginStr) throws SQLException {
    String str = stripOriginStr.toLowerCase();
    String schema = str.substring(useSchema_.length()).trim();
    logger.info("using schema: {}", schema);
    checkSchema(schema);
    connectContext.setSchema(getRealSchema(schema));
    return true;
  }

  private boolean useVCluster(String stripOriginStr) throws SQLException {
    String vc = stripOriginStr.substring(useVcluster_.length()).trim();
    logger.info("using virtual cluster: {}", vc);
    if (vc.contains(" ")) {
      throw new SQLException("invalid vcluster: " + vc);
    }
    connectContext.setVc(vc);
    return true;
  }

  private boolean useSchemaAlias(String stripOriginStr) throws SQLException {
    String str = stripOriginStr.toLowerCase();
    String schema = str.substring(use_.length()).trim();
    logger.info("using schema: {}", schema);
    checkSchema(schema);
    connectContext.setSchema(getRealSchema(schema));
    return true;
  }

  private boolean setUserId(String stripOriginStr) throws SQLException {
    String str = stripOriginStr.toLowerCase();
    if (connectContext.getRunningMode() != CZConnectContext.RunningMode.COORDINATOR &&
            connectContext.getRunningMode() != CZConnectContext.RunningMode.COORDINATOR_SERVICE) {
      throw new SQLException("Set user id is not supported in running mode " + connectContext.getRunningMode());
    }
    String equalStr = str.substring(setUserId.length()).trim();
    if (equalStr.startsWith("=")) {
      long newUserId = Long.parseLong(equalStr.substring("=".length()).trim());
      logger.info("setting user id: {}", newUserId);
      connectContext.setUserId(newUserId);
      return true;
    } else {
      throw new SQLException("Invalid statement: " + str);
    }
  }

  private boolean setInstanceId(String stripOriginStr) throws SQLException {
    String str = stripOriginStr.toLowerCase();
    if (connectContext.getRunningMode() != CZConnectContext.RunningMode.COORDINATOR &&
            connectContext.getRunningMode() != CZConnectContext.RunningMode.COORDINATOR_SERVICE) {
      throw new SQLException("Set instance id is not supported in running mode " + connectContext.getRunningMode());
    }
    String equalStr = str.substring(setInstanceId.length()).trim();
    if (equalStr.startsWith("=")) {
      long newInstanceId = Long.parseLong(equalStr.substring("=".length()).trim());
      logger.info("setting instance id: {}", newInstanceId);
      connectContext.setInstanceId(newInstanceId);
      return true;
    } else {
      throw new SQLException("Invalid statement " + str);
    }
  }

  private boolean setWorkspace(String stripOriginStr) throws SQLException {
    String str = stripOriginStr.toLowerCase();
    if (connectContext.getRunningMode() != CZConnectContext.RunningMode.COORDINATOR &&
            connectContext.getRunningMode() != CZConnectContext.RunningMode.COORDINATOR_SERVICE) {
      throw new SQLException("Set workspace is not supported in running mode " + connectContext.getRunningMode());
    }
    String equalStr = str.substring(setWorkspace.length()).trim();
    if (equalStr.startsWith("=")) {
      String newWorkspace = equalStr.substring("=".length()).trim();
      logger.info("setting workspace: {}", newWorkspace);
      connectContext.setWorkspace(newWorkspace);
      return true;
    } else {
      throw new SQLException("Invalid statement " + str);
    }
  }

  private boolean setMode(String stripOriginStr) throws SQLException {
    String str = stripOriginStr.toLowerCase();
    String equalStr = str.substring(setMode.length()).trim();
    if (equalStr.startsWith("=")) {
      String mode = equalStr.substring("=".length()).trim();
      logger.info("setting jdbc mode: {}", mode);
      if (mode.equals("hybrid")) {
        connectContext.setJDBCMode(1);
      } else {
        connectContext.setJDBCMode(0);
      }
      return true;
    } else {
      throw new SQLException("Invalid statement " + str);
    }
  }

  private boolean setPollingTimeout(String stripOriginStr) throws SQLException {
    String str = stripOriginStr.toLowerCase();
    String equalStr = str.substring(setPollingTimeout.length()).trim();
    if (equalStr.startsWith("=")) {
      String timeoutStr = equalStr.substring("=".length()).trim();
      logger.info("setting polling timeout: {}", timeoutStr);
      connectContext.setHybridPollingTimeout(Integer.parseInt(timeoutStr));
      return true;
    } else {
      throw new SQLException("Invalid statement " + str);
    }
  }

  private boolean setQueryTimeout(String stripOriginStr) throws SQLException {
    String str = stripOriginStr.toLowerCase();
    String equalStr = str.substring(setQueryTimeout.length()).trim();
    if (equalStr.startsWith("=")) {
      String timeoutStr = equalStr.substring("=".length()).trim();
      logger.info("setting query timeout: {}", timeoutStr);
      connectContext.setQueryTimeout(Integer.parseInt(timeoutStr));
      return true;
    } else {
      throw new SQLException("Invalid statement " + str);
    }
  }

  private boolean setMaxRowSize(String stripOriginStr) throws SQLException {
    String str = stripOriginStr.toLowerCase();
    String equalStr = str.substring(setMaxRowSize.length()).trim();
    if (equalStr.startsWith("=")) {
      String rowSize = equalStr.substring("=".length()).trim();
      logger.info("setting max row size: {}", rowSize);
      connectContext.setMaxRowSize(Integer.parseInt(rowSize));
      return true;
    } else {
      throw new SQLException("Invalid statement " + str);
    }
  }

  private boolean setQueryTag(String stripOriginStr) throws SQLException {
    String equalStr = stripOriginStr.substring(setQueryTag.length()).trim();
    if (equalStr.startsWith("=")) {
      String queryTag = parseStrInQuote(equalStr.substring("=".length()).trim());
      logger.info("setting query tag: {}", queryTag);
      connectContext.setQueryTag(queryTag);
      return true;
    } else {
      throw new SQLException("Invalid statement " + stripOriginStr);
    }
  }

  private boolean setCopyCsvDelimiter(String stripOriginStr) throws SQLException {
    String equalStr = stripOriginStr.substring(setCopyCsvDelimiter.length()).trim();
    if (equalStr.startsWith("=")) {
      String delimiterStr = parseStrInQuote(equalStr.substring("=".length()).trim());
      logger.info("setting csv field delimiter: '{}'", delimiterStr);
      if (CZUtil.escapeMap.containsKey(delimiterStr)) {
        connectContext.setCsvFieldDelimiter((Character) CZUtil.escapeMap.get(delimiterStr));
        return true;
      } else if (delimiterStr.length() == 1) {
        connectContext.setCsvFieldDelimiter(delimiterStr.charAt(0));
        return true;
      }
    }
    throw new SQLException("Invalid statement." + stripOriginStr);
  }

  private boolean setCopyCsvFieldDelimiter(String stripOriginStr) throws SQLException {
    String equalStr = stripOriginStr.substring(setCopyCsvFieldDelimiter.length()).trim();
    if (equalStr.startsWith("=")) {
      String delimiterStr = parseStrInQuote(equalStr.substring("=".length()).trim());
      logger.info("setting csv field delimiter: '{}'", delimiterStr);
      if (CZUtil.escapeMap.containsKey(delimiterStr)) {
        connectContext.setCsvFieldDelimiter((Character) CZUtil.escapeMap.get(delimiterStr));
        return true;
      } else if (delimiterStr.length() == 1) {
        connectContext.setCsvFieldDelimiter(delimiterStr.charAt(0));
        return true;
      }
    }
    throw new SQLException("Invalid statement." + stripOriginStr);
  }

  private boolean setCopyCsvEscape(String stripOriginStr) throws SQLException {
    String equalStr = stripOriginStr.substring(setCopyCsvEscape.length()).trim();
    if (equalStr.startsWith("=")) {
      String escapeStr = parseStrInQuote(equalStr.substring("=".length()).trim());
      if (escapeStr.length() == 1) {
        connectContext.setCsvEscapeChar(escapeStr.charAt(0));
      } else if (escapeStr.length() > 1) {
        connectContext.setCsvEscapeChar(StringEscapeUtils.escapeJava(escapeStr).charAt(0));
      }
      logger.info("setting csv escape: '{}'", connectContext.getCsvEscapeChar());
      return true;
    }
    throw new SQLException("Invalid statement " + stripOriginStr);
  }

  private boolean setCopyCsvWithHeader(String stripOriginStr) throws SQLException {
    String str = stripOriginStr.toLowerCase();
    String equalStr = str.substring(setCopyCsvWithHeader.length()).trim();
    if (equalStr.startsWith("=")) {
      String value = equalStr.substring("=".length()).trim();
      logger.info("setting csv with header: {}", value);
      if (value.equalsIgnoreCase("false")) {
        connectContext.setCsvWithHeader(false);
        return true;
      } else if (value.equalsIgnoreCase("true")) {
        connectContext.setCsvWithHeader(true);
        return true;
      }
    }
    throw new SQLException("Invalid statement." + str);
  }

  private boolean setCopyCsvSkipHeader(String stripOriginStr) throws SQLException {
    String str = stripOriginStr.toLowerCase();
    String equalStr = str.substring(setCopyCsvSkipHeader.length()).trim();
    if (equalStr.startsWith("=")) {
      String skipStr = equalStr.substring("=".length()).trim();
      logger.info("setting csv skip header: {}", skipStr);
      if (skipStr.equalsIgnoreCase("false")) {
        connectContext.setCsvSkipHeaer(false);
        return true;
      } else if (skipStr.equalsIgnoreCase("true")) {
        connectContext.setCsvSkipHeaer(true);
        return true;
      }
    }
    throw new SQLException("Invalid statement " + str);
  }

  private boolean setCopyCsvNullString(String stripOriginStr) throws SQLException {
    String equalStr = stripOriginStr.substring(setCopyCsvNullString.length()).trim();
    if (equalStr.startsWith("=")) {
      String nullStr = parseStrInQuote(equalStr.substring("=".length()).trim());
      logger.info("setting csv null string: '{}'", nullStr);
      connectContext.setCsvNullString(nullStr);
      return true;
    }
    throw new SQLException("Invalid statement " + stripOriginStr);
  }

  private boolean setCopyCsvIgnoreMalformedRecord(String stripOriginStr) throws SQLException {
    String equalStr = stripOriginStr.substring(setCopyCsvIgnoreMalformedRecord.length()).trim();
    if (equalStr.startsWith("=")) {
      String value = parseStrInQuote(equalStr.substring("=".length()).trim());
      logger.info("setting csv ignore malformed record: {}", value);
      connectContext.setCsvIgnoreMalformedRecord(Long.parseLong(value));
      return true;
    }
    throw new SQLException("Invalid set copy.csv.ignore.malformed.record statement." + stripOriginStr);
  }

  private boolean setCopyCsvTimeZonePlus(String stripOriginStr) throws SQLException {
    String equalStr = stripOriginStr.substring(setCopyCsvTimeZonePlus.length()).trim();
    if (equalStr.startsWith("=")) {
      String value = parseStrInQuote(equalStr.substring("=".length()).trim());
      logger.info("{} {}", setCopyCsvTimeZonePlus, value);
      connectContext.setCsvTimeZonePlus(Long.parseLong(value));
      return true;
    }
    throw new SQLException("Invalid set copy.csv.timezone.plus. " + stripOriginStr);
  }

  private boolean setPriority(String stripOriginStr) throws SQLException {
    String equalStr = stripOriginStr.substring(setPriority.length()).trim();
    if (equalStr.startsWith("=")) {
      String priorityStr = equalStr.substring("=".length()).trim();
      logger.info("setting priority: {}", priorityStr);
      connectContext.setPriorityInString(priorityStr);
      return true;
    } else {
      throw new SQLException("Invalid statement " + stripOriginStr);
    }
  }

  private boolean setScheduleJobPriority(String stripOriginStr) throws SQLException {
    String equalStr = stripOriginStr.substring(setScheduleJobPriority.length()).trim();
    if (equalStr.startsWith("=")) {
      String priorityStr = equalStr.substring("=".length()).trim();
      logger.info("setting priority: {}", priorityStr);
      connectContext.setPriorityInString(priorityStr);
      return true;
    } else {
      throw new SQLException("Invalid statement " + stripOriginStr);
    }
  }

  private boolean setConfig(String stripOriginStr) throws SQLException {
    String str = stripOriginStr.toLowerCase();
    logger.info("setting config: " + str);
    String setStatement = stripOriginStr.substring(setConfig_.length());
    List setKeyValue = CZUtil.querySplit2(setStatement, '=', 1);
    if (setKeyValue.size() == 2 && !setKeyValue.get(0).trim().isEmpty() && !setKeyValue.get(1).trim().isEmpty()) {
      logger.info(String.format("setting config: '%s' = '%s'", setKeyValue.get(0).trim(), setKeyValue.get(1).trim()));
      connectContext.setConfig(setKeyValue.get(0).trim(), setKeyValue.get(1).trim());
      return true;
    } else {
      throw new SQLException("Invalid statement " + stripOriginStr);
    }
  }

  private boolean showContext(String stripOriginStr) throws SQLException {
    logger.info("showing context:");
    Map configs = connectContext.getConfigs();
    List columnMetaDatas = new ArrayList<>();
    columnMetaDatas.add(new CZColumnMetaData("Instance", "VARCHAR",
            Types.VARCHAR));
    columnMetaDatas.add(new CZColumnMetaData("Workspace", "VARCHAR",
            java.sql.Types.VARCHAR));
    columnMetaDatas.add(new CZColumnMetaData("Schema", "VARCHAR",
            java.sql.Types.VARCHAR));
    List> rows = new ArrayList<>();
    List row = new ArrayList<>();
    row.add(connectContext.getLakeHouseName());
    row.add(connectContext.getWorkspace());
    row.add(connectContext.getSchema());
    rows.add(row);
    CZResult dataResult = new CZResultListString(rows, columnMetaDatas);
    resultSet = new CZTextResultSet(columnMetaDatas, dataResult);
    return true;
  }

  private boolean showConfig(String stripOriginStr) throws SQLException {
    logger.info("showing config:");
    Map configs = connectContext.getConfigs();
    List columnMetaDatas = new ArrayList<>();
    columnMetaDatas.add(new CZColumnMetaData("Key", "VARCHAR",
            java.sql.Types.VARCHAR));
    columnMetaDatas.add(new CZColumnMetaData("Value", "VARCHAR",
            java.sql.Types.VARCHAR));
    List rows = new ArrayList<>();
    for (Map.Entry entry : configs.entrySet()) {
      rows.add(String.format("%s,%s", entry.getKey(), entry.getValue()));
    }
    CZResult dataResult = new CZInMemoryTextResult(rows, columnMetaDatas, 10000);
    resultSet = new CZTextResultSet(columnMetaDatas, dataResult);
    warning = String.join(";", rows);
    return true;
  }

  private boolean clearConfig(String stripOriginStr) throws SQLException {
    logger.info("clearing config");
    connectContext.resetConfigs();
    return true;
  }

  private boolean printDebug(String stripOriginStr) throws SQLException {
    logger.info("debug on");
    connectContext.setShowDebug(true);
    return true;
  }

  private boolean printNoDebug(String stripOriginStr) throws SQLException {
    logger.info("debug off");
    connectContext.setShowDebug(false);
    return true;
  }

  private boolean copyCommand(String stripOriginStr) throws SQLException {
    String tableAndLocation = stripOriginStr.substring(copy_.length());
    Pattern patterInto = Pattern.compile("(?s)(?i)\\s*into\\s+(.*)");
    Pattern patternOverwrite = Pattern.compile("(?s)(?i)\\s*overwrite\\s+(.*)");
    Matcher matcher = patterInto.matcher(tableAndLocation);
    if (matcher.matches()){
      return false;
    } else {
      matcher = patternOverwrite.matcher(tableAndLocation);
      if (matcher.matches()) {
        return false;
      }
    }
    Pattern patternTo = Pattern.compile("(?s)(?i)(.*)\\s+to\\s+(.*)");
    Pattern patternFrom = Pattern.compile("(?s)(?i)(.*)\\s+from\\s+(.*)");
    matcher = patternTo.matcher(tableAndLocation);
    if (matcher.matches()) {
      return copyTo(tableAndLocation, matcher);
    } else {
      matcher = patternFrom.matcher(tableAndLocation);
      if (matcher.matches()) {
        return copyFrom(tableAndLocation, matcher);
      }
    }
    return false;
  }

  private boolean copyTo(String tableAndLocation, Matcher matcher) throws SQLException {
    if (matcher.groupCount() != 2) {
      throw new SQLException("Invalid copy to " + tableAndLocation);
    }
    List tableLocation = new ArrayList<>();
    tableLocation.add(matcher.group(1).trim());
    tableLocation.add(matcher.group(2).trim());
    commandType = CommandType.COPY_TO;
    copyTable = tableLocation.get(0).trim();
    copyLocation = parseStrInQuote(tableLocation.get(1).trim());
    logger.info("copying table '{}' to location '{}'", copyTable, copyLocation);
    if (copyLocation.contains(" ")) {
      throw new SQLException("Invalid copy location: " + copyLocation);
    }
    return false; // copy to is actually a sql, return false here to go to submit sql
  }

  private boolean copyFrom(String tableAndLocation, Matcher matcher) throws SQLException {
    if (matcher.groupCount() != 2) {
      throw new SQLException("Invalid copy from " + tableAndLocation);
    }
    List tableLocation = new ArrayList<>();
    tableLocation.add(matcher.group(1).trim());
    tableLocation.add(matcher.group(2).trim());
    commandType = CommandType.COPY_FROM;
    copyTable = tableLocation.get(0).trim();
    copyLocation = parseStrInQuote(tableLocation.get(1).trim());
    List tablePartition = CZUtil.parseTableAndPartition(copyTable);
    Map copyPartitionSpec = new HashMap<>();
    if (tablePartition.size() == 2 && tablePartition.get(1).trim().startsWith("(")) {
      copyTable = tablePartition.get(0).trim();
      copyPartitionSpec = CZUtil.parsePartitionSpec(tablePartition.get(1).trim());
    }
    if (copyLocation.contains(" ")) {
      throw new SQLException("Invalid copy location: " + copyLocation);
    }
    logger.info("copying table '{}' from location '{}'", copyTable, copyLocation);
    CSVFormat csvFormat;
    if (connectContext.getCsvWithHeader()) {
      csvFormat =
              CSVFormat.DEFAULT.withDelimiter(connectContext.getCsvFieldDelimiter())
                      .withNullString(connectContext.getCsvNullString()).withFirstRecordAsHeader();
    } else {
      csvFormat =
              CSVFormat.DEFAULT.withDelimiter(connectContext.getCsvFieldDelimiter())
                      .withNullString(connectContext.getCsvNullString());
    }
    if (connectContext.getCsvEscapeChar() != '\u0000') {
      csvFormat = csvFormat.withEscape(connectContext.getCsvEscapeChar());
    }
    logger.info(csvFormat.toString());
    if (connectContext.getRunningMode() != CZConnectContext.RunningMode.DRYRUN) {
      CZUtil.loadFromLocation(copyLocation,
              csvFormat, connectContext,
              copyTable, copyPartitionSpec,
              connectContext.getCsvWithHeader(),
              connectContext.getCsvSkipHeader(),
              connectContext.getCsvTimeZonePlus());
    }
    return true;
  }

  private boolean putVolume(String stripOriginStr) throws SQLException {
    String str = stripOriginStr.toLowerCase();
    Pattern pattern = Pattern.compile("(?i)put\\s+([^ ]+)\\s+to\\s+volume\\s+([^ ]+)(.*)");
    Matcher matcher = pattern.matcher(str);
    if (matcher.matches()) {
      commandType = CommandType.PUT_VOLUME;
      connectContext.setLocalFilePath(matcher.group(1));
      connectContext.setVolumePath(matcher.group(2));
      String paramsStr = matcher.group(3).trim();
      String[] params = paramsStr.split("\\s+");
      if (params.length > 0) {
        for (String param : params) {
          if (param.trim().length() > 0) {
            String[] kv = param.split("=");
            if (kv.length == 2) {
              if (kv[0].equalsIgnoreCase("PARALLEL")) {
                connectContext.setVolumeParallel(Integer.parseInt(kv[1]));
              } else if (kv[0].equalsIgnoreCase("AUTO_COMPRESS")) {
                connectContext.setVolumeAutoCompress(Boolean.parseBoolean(kv[1]));
              } else if (kv[0].equalsIgnoreCase("SOURCE_COMPRESSION")) {
                connectContext.setVolumeSourceCompressionType(kv[1]);
              }
            } else {
              throw new SQLException("Invalid put command " + stripOriginStr);
            }
          }
        }
      }
      return false;
    } else {
      throw new SQLException("Invalid put command " + stripOriginStr);
    }
  }

  private boolean getVolume(String stripOriginStr) throws SQLException {
    String str = stripOriginStr.toLowerCase();
    Pattern pattern = Pattern.compile("(?i)get volume\\s+([^ ]+)\\s+to\\s([^ ]+)(.*)");
    Matcher matcher = pattern.matcher(str);
    if (matcher.matches()) {
      commandType = CommandType.GET_VOLUME;
      connectContext.setVolumePath(matcher.group(1));
      connectContext.setLocalFilePath(matcher.group(2));
      String paramsStr = matcher.group(3).trim();
      String[] params = paramsStr.split("\\s+");
      if (params.length > 0) {
        for (String param : params) {
          if (param.trim().length() > 0) {
            String[] kv = param.split("=");
            if (kv.length == 2) {
              if (kv[0].equalsIgnoreCase("PARALLEL")) {
                connectContext.setVolumeParallel(Integer.parseInt(kv[1]));
              } else if (kv[0].equalsIgnoreCase("PATTERN")) {
                connectContext.setVolumePattern(kv[1]);
              }
            } else {
              throw new SQLException("Invalid get command " + stripOriginStr);
            }
          }
        }
      }
      return false;
    } else {
      throw new SQLException("Invalid get command " + stripOriginStr);
    }
  }

  private boolean setVolumeDownload(String stripOriginStr) throws SQLException {
    String str = stripOriginStr.toLowerCase();
    String equalStr = str.substring(setVolumeDownload.length()).trim();
    if (equalStr.startsWith("=")) {
      String downloadFlag = equalStr.substring("=".length()).trim();
      logger.info("setting volume download flag: {}", downloadFlag);
      connectContext.setVolumeDownloadFlag(Boolean.parseBoolean(downloadFlag));
      return true;
    } else {
      throw new SQLException("Invalid statement " + str);
    }
  }

  private void checkSchema(String schema) throws SQLException {
    if (schema == null || schema.isEmpty()) {
      throw new SQLException("Schema is empty");
    }
    String sql = "desc schema " + schema;
    try (CZStatement statement = new CZStatement(connectContext)) {
      statement.execute(sql);
    } catch (SQLException e) {
      if (e.getMessage().contains("schema not found")) {
        throw new SQLException("Schema " + schema + " not found");
      } else {
        throw e;
      }
    }
  }

  private String checkRemoveQuote(String schema) {
    if (schema.startsWith("`") && schema.endsWith("`")) {
      schema = schema.substring(1, schema.length() - 1);
    }
    return schema;
  }

  private String getRealSchema(String schema) {
    String[] schemaParts = schema.split("\\.");
    return checkRemoveQuote(schemaParts[schemaParts.length - 1]);
  }

  private boolean setJdbcQueryTimeout(String stripOriginStr) throws SQLException {
    String str = stripOriginStr.toLowerCase();
    String equalStr = str.substring(setJdbcQueryTimeout.length()).trim();
    if (equalStr.startsWith("=")) {
      String timeoutStr = equalStr.substring("=".length()).trim();
      logger.info("setting jdbc query timeout: {}", timeoutStr);
      queryTimeoutMs = Integer.parseInt(timeoutStr);
      connectContext.setJdbcQueryTimeoutMs(queryTimeoutMs);
      return true;
    } else {
      throw new SQLException("Invalid statement " + str);
    }
  }
}