content: /*
   * Copyright (C) 2016-2023 ActionTech.
   * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher.
   */
  
  package com.actiontech.dble.config.helper;
  
  import com.actiontech.dble.backend.datasource.PhysicalDbInstance;
  import com.actiontech.dble.backend.mysql.VersionUtil;
  import com.actiontech.dble.config.Isolations;
  import com.actiontech.dble.config.model.SystemConfig;
  import com.actiontech.dble.meta.ReloadLogHelper;
  import com.actiontech.dble.sqlengine.*;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.util.Map;
  import java.util.concurrent.Callable;
  import java.util.concurrent.TimeUnit;
  import java.util.concurrent.locks.Condition;
  import java.util.concurrent.locks.ReentrantLock;
  
  public class GetAndSyncDbInstanceKeyVariables implements Callable<KeyVariables> {
      private static final Logger LOGGER = LoggerFactory.getLogger(GetAndSyncDbInstanceKeyVariables.class);
      private static final String COLUMN_LOWER_CASE = "@@lower_case_table_names";
      private static final String COLUMN_AUTOCOMMIT = "@@autocommit";
      private static final String COLUMN_READONLY = "@@read_only";
      private static final String COLUMN_MAX_PACKET = "@@max_allowed_packet";
      private static final String COLUMN_VERSION = "@@version";
      private static final String COLUMN_BACK_LOG = "@@back_log";
      private ReentrantLock lock = new ReentrantLock();
      private volatile boolean isFinish = false;
      private Condition finishCond = lock.newCondition();
      private volatile KeyVariables keyVariables;
      private PhysicalDbInstance ds;
      private final String columnIsolation;
      private final boolean needSync;
      private volatile boolean fail = false;
  
      public GetAndSyncDbInstanceKeyVariables(PhysicalDbInstance ds, boolean needSync) {
          this.ds = ds;
          this.needSync = needSync;
          String isolationName = VersionUtil.getIsolationNameByVersion(ds.getDsVersion());
          if (isolationName != null) {
              columnIsolation = "@@" + isolationName;
          } else {
              columnIsolation = null;
          }
      }
  
      @Override
      public KeyVariables call() {
          if (columnIsolation == null) {
              return keyVariables;
          }
          String[] columns = new String[]{COLUMN_LOWER_CASE, COLUMN_AUTOCOMMIT, COLUMN_READONLY, COLUMN_MAX_PACKET, columnIsolation, COLUMN_VERSION, COLUMN_BACK_LOG};
          StringBuilder sql = new StringBuilder("select ");
          for (int i = 0; i < columns.length; i++) {
              if (i != 0) {
                  sql.append(",");
              }
              sql.append(columns[i]);
          }
          OneRawSQLQueryResultHandler resultHandler = new OneRawSQLQueryResultHandler(columns, new GetDbInstanceKeyVariablesListener());
          OneTimeConnJob sqlJob = new OneTimeConnJob(sql.toString(), null, resultHandler, ds);
          sqlJob.run();
          lock.lock();
          try {
              while (!isFinish) {
                  boolean await = finishCond.await(ds.getConfig().getPoolConfig().getHeartbeatPeriodMillis(), TimeUnit.MILLISECONDS);
                  if (!await) {
                      if (!isFinish) {
                          fail = true;
                          isFinish = true;
                          LOGGER.warn("test conn timeout,TCP connection may be lost");
                      }
                  }
              }
          } catch (InterruptedException e) {
              LOGGER.warn("test conn Interrupted:", e);
          } finally {
              lock.unlock();
              ReloadLogHelper.debug("get key variables :{},dbInstance:{},result:{}", sql.toString(), ds, keyVariables);
          }
          return keyVariables;
      }
  
      private class GetDbInstanceKeyVariablesListener implements SQLQueryResultListener<SQLQueryResult<Map<String, String>>> {
          @Override
          public void onResult(SQLQueryResult<Map<String, String>> result) {
              boolean isSuccess = result.isSuccess();
              ds.setTestConnSuccess(isSuccess);
              if (isFinish) {
                  if (fail)
                      LOGGER.info("test conn timeout, need to resynchronize");
                  return;
              }
              KeyVariables keyVariablesTmp = null;
              if (isSuccess) {
                  keyVariablesTmp = new KeyVariables();
                  keyVariablesTmp.setLowerCase(!result.getResult().get(COLUMN_LOWER_CASE).equals("0"));
  
                  keyVariablesTmp.setAutocommit(result.getResult().get(COLUMN_AUTOCOMMIT).equals("1"));
                  keyVariablesTmp.setTargetAutocommit(SystemConfig.getInstance().getAutocommit() == 1);
  
                  String isolation = result.getResult().get(columnIsolation);
                  switch (isolation) {
                      case "READ-COMMITTED":
                          keyVariablesTmp.setIsolation(Isolations.READ_COMMITTED);
                          break;
                      case "READ-UNCOMMITTED":
                          keyVariablesTmp.setIsolation(Isolations.READ_UNCOMMITTED);
                          break;
                      case "REPEATABLE-READ":
                          keyVariablesTmp.setIsolation(Isolations.REPEATABLE_READ);
                          break;
                      case "SERIALIZABLE":
                          keyVariablesTmp.setIsolation(Isolations.SERIALIZABLE);
                          break;
                      default:
                          break;
                  }
                  keyVariablesTmp.setTargetIsolation(SystemConfig.getInstance().getTxIsolation());
                  keyVariablesTmp.setMaxPacketSize(Integer.parseInt(result.getResult().get(COLUMN_MAX_PACKET)));
                  keyVariablesTmp.setTargetMaxPacketSize(SystemConfig.getInstance().getMaxPacketSize() + KeyVariables.MARGIN_PACKET_SIZE);
                  keyVariablesTmp.setReadOnly(result.getResult().get(COLUMN_READONLY).equals("1"));
                  keyVariablesTmp.setVersion(result.getResult().get(COLUMN_VERSION));
                  keyVariablesTmp.setBackLog(Integer.parseInt(result.getResult().get(COLUMN_BACK_LOG)));
  
                  if (needSync) {
                      boolean checkNeedSync = false;
                      if (keyVariablesTmp.getTargetMaxPacketSize() > keyVariablesTmp.getMaxPacketSize()) {
                          checkNeedSync = true;
                      }
                      if (keyVariablesTmp.isTargetAutocommit() != keyVariablesTmp.isAutocommit()) {
                          checkNeedSync = true;
                      } else {
                          ds.setAutocommitSynced(true);
                      }
                      if (keyVariablesTmp.getTargetIsolation() != keyVariablesTmp.getIsolation()) {
                          checkNeedSync = true;
                      } else {
                          ds.setIsolationSynced(true);
                      }
  
                      if (checkNeedSync) {
                          SyncDbInstanceKeyVariables task = new SyncDbInstanceKeyVariables(keyVariablesTmp, ds);
                          boolean synced = false;
                          try {
                              synced = task.call();
                          } catch (Exception e) {
                              LOGGER.warn("SyncDbInstanceKeyVariables error", e);
                          }
  
                          if (synced) {
                              ds.setAutocommitSynced(true);
                              ds.setIsolationSynced(true);
                              keyVariablesTmp.setMaxPacketSize(keyVariablesTmp.getTargetMaxPacketSize());
                          }
                      }
                  }
              }
              handleFinished(keyVariablesTmp);
          }
  
          private void handleFinished(KeyVariables keyVariablesTmp) {
              lock.lock();
              try {
                  if (isFinish) {
                      if (fail)
                          LOGGER.info("test conn timeout, need to resynchronize");
                  } else {
                      keyVariables = keyVariablesTmp;
                      isFinish = true;
                  }
                  finishCond.signal();
              } finally {
                  lock.unlock();
              }
          }
      }
  }
  