content: /*
   * Copyright (C) 2016-2023 ActionTech.
   * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher.
   */
  
  package com.actiontech.dble.server.response;
  
  import com.actiontech.dble.DbleServer;
  import com.actiontech.dble.backend.mysql.PacketUtil;
  import com.actiontech.dble.backend.mysql.nio.handler.ShowTablesHandler;
  import com.actiontech.dble.backend.mysql.nio.handler.query.DMLResponseHandler;
  import com.actiontech.dble.backend.mysql.nio.handler.util.HandlerTool;
  import com.actiontech.dble.config.ErrorCode;
  import com.actiontech.dble.config.Fields;
  import com.actiontech.dble.config.model.sharding.SchemaConfig;
  import com.actiontech.dble.config.model.sharding.table.BaseTableConfig;
  import com.actiontech.dble.config.model.sharding.table.GlobalTableConfig;
  import com.actiontech.dble.config.model.sharding.table.ShardingTableConfig;
  import com.actiontech.dble.config.model.sharding.table.SingleTableConfig;
  import com.actiontech.dble.config.model.user.ShardingUserConfig;
  import com.actiontech.dble.meta.SchemaMeta;
  import com.actiontech.dble.meta.ViewMeta;
  import com.actiontech.dble.net.mysql.*;
  import com.actiontech.dble.plan.common.field.Field;
  import com.actiontech.dble.plan.common.item.Item;
  import com.actiontech.dble.plan.visitor.MySQLItemVisitor;
  import com.actiontech.dble.route.RouteResultset;
  import com.actiontech.dble.route.util.RouterUtil;
  import com.actiontech.dble.server.parser.ServerParse;
  import com.actiontech.dble.statistic.trace.TraceResult;
  import com.actiontech.dble.services.mysqlsharding.ShardingService;
  import com.actiontech.dble.singleton.ProxyMeta;
  import com.actiontech.dble.util.StringUtil;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.nio.ByteBuffer;
  import java.util.*;
  import java.util.regex.Pattern;
  
  /**
   * show tables impl
   *
   * @author yanglixue
   */
  public final class ShowTables {
      private ShowTables() {
      }
  
      protected static final Logger LOGGER = LoggerFactory.getLogger(ShardingService.class);
  
      public static void response(ShardingService shardingService, String stmt) {
          ShowTablesStmtInfo info;
          try {
              info = new ShowTablesStmtInfo(stmt);
          } catch (Exception e) {
              shardingService.writeErrMessage(ErrorCode.ER_PARSE_ERROR, e.toString());
              return;
          }
          if (info.getLike() != null && info.getWhere() != null) {
              shardingService.writeErrMessage("42000", "only allow LIKE or WHERE clause in statement", ErrorCode.ER_PARSE_ERROR);
              return;
          }
          String showSchema = info.getSchema();
          if (showSchema != null && DbleServer.getInstance().getSystemVariables().isLowerCaseTableNames()) {
              showSchema = showSchema.toLowerCase();
          }
          String cSchema = showSchema == null ? shardingService.getSchema() : showSchema;
          if (cSchema == null) {
              shardingService.writeErrMessage("3D000", "No database selected", ErrorCode.ER_NO_DB_ERROR);
              return;
          }
          SchemaConfig schema = DbleServer.getInstance().getConfig().getSchemas().get(cSchema);
          if (schema == null) {
              shardingService.writeErrMessage("42000", "Unknown database '" + cSchema + "'", ErrorCode.ER_BAD_DB_ERROR);
              return;
          }
  
          ShardingUserConfig user = (ShardingUserConfig) (DbleServer.getInstance().getConfig().getUsers().get(shardingService.getUser()));
          if (user == null || !user.getSchemas().contains(cSchema)) {
              shardingService.writeErrMessage("42000", "Access denied for user '" + shardingService.getUser().getFullName() + "' to database '" + cSchema + "'", ErrorCode.ER_DBACCESS_DENIED_ERROR);
              return;
          }
          //if sharding has default single node ,show tables will send to backend
          if (schema.isDefaultSingleNode()) {
              String node = schema.getDefaultSingleNode();
              try {
                  parserAndExecuteShowTables(shardingService, stmt, node, info);
              } catch (Exception e) {
                  shardingService.writeErrMessage(ErrorCode.ER_PARSE_ERROR, e.toString());
              }
          } else {
              responseDirect(shardingService, cSchema, info);
          }
      }
  
      private static void parserAndExecuteShowTables(ShardingService shardingService, String originSql, String node, ShowTablesStmtInfo info) throws Exception {
          RouteResultset rrs = new RouteResultset(originSql, ServerParse.SHOW);
          if (info.getSchema() != null || info.isAll()) {
              StringBuilder sql = new StringBuilder();
              sql.append("SHOW ");
              if (info.isFull()) {
                  sql.append("FULL ");
              }
              sql.append("TABLES ");
              if (info.getCond() != null) {
                  sql.append(info.getCond());
              }
              rrs.setStatement(sql.toString());
          }
          RouterUtil.routeToSingleNode(rrs, node, null);
          ShowTablesHandler showTablesHandler = new ShowTablesHandler(rrs, shardingService.getSession2(), info);
          shardingService.getSession2().trace(t -> t.setPreExecuteEnd(TraceResult.SqlTraceType.SINGLE_NODE_QUERY));
          showTablesHandler.execute();
      }
  
      private static void responseDirect(ShardingService shardingService, String cSchema, ShowTablesStmtInfo info) {
  
          ByteBuffer buffer = shardingService.allocate();
          Map<String, String> tableMap = getTableSet(cSchema, info);
          PackageBufINf bufInf;
          String schemaColumn = cSchema;
          if (info.getLike() != null) {
              schemaColumn = schemaColumn + " (" + info.getLike() + ")";
          }
          if (info.isFull()) {
              List<FieldPacket> fieldPackets = new ArrayList<>(2);
              bufInf = writeFullTablesHeader(buffer, shardingService, schemaColumn, fieldPackets);
              if (info.getWhere() != null) {
                  MySQLItemVisitor mev = new MySQLItemVisitor(shardingService.getSchema(), shardingService.getCharset().getResultsIndex(), ProxyMeta.getInstance().getTmManager(), shardingService.getUsrVariables(), null);
                  info.getWhereExpr().accept(mev);
                  List<Field> sourceFields = HandlerTool.createFields(fieldPackets);
                  Item whereItem = HandlerTool.createItem(mev.getItem(), sourceFields, 0, false, DMLResponseHandler.HandlerType.WHERE);
                  bufInf = writeFullTablesRow(bufInf.getBuffer(), shardingService, tableMap, whereItem, sourceFields);
              } else {
                  bufInf = writeFullTablesRow(bufInf.getBuffer(), shardingService, tableMap, null, null);
              }
          } else {
              bufInf = writeTablesHeaderAndRows(buffer, shardingService, tableMap, schemaColumn);
          }
          writeRowEof(bufInf.getBuffer(), shardingService);
      }
  
      public static PackageBufINf writeFullTablesHeader(ByteBuffer buffer, ShardingService shardingService, String cSchema, List<FieldPacket> fieldPackets) {
          int fieldCount = 2;
          ResultSetHeaderPacket header = PacketUtil.getHeader(fieldCount);
          FieldPacket[] fields = new FieldPacket[fieldCount];
          int i = 0;
          header.setPacketId(shardingService.nextPacketId());
          fields[i] = PacketUtil.getField("Tables_in_" + cSchema, Fields.FIELD_TYPE_VAR_STRING);
          fields[i].setPacketId(shardingService.nextPacketId());
          fieldPackets.add(fields[i]);
          fields[i + 1] = PacketUtil.getField("Table_type", Fields.FIELD_TYPE_VAR_STRING);
          fields[i + 1].setPacketId(shardingService.nextPacketId());
          fieldPackets.add(fields[i + 1]);
  
          EOFPacket eof = new EOFPacket();
          eof.setPacketId(shardingService.nextPacketId());
          // writeDirectly header
          buffer = header.write(buffer, shardingService, true);
          // writeDirectly fields
          for (FieldPacket field : fields) {
              buffer = field.write(buffer, shardingService, true);
          }
          eof.write(buffer, shardingService, true);
          PackageBufINf packBuffInfo = new PackageBufINf();
          packBuffInfo.setBuffer(buffer);
          return packBuffInfo;
      }
  
      public static PackageBufINf writeFullTablesRow(ByteBuffer buffer, ShardingService shardingService, Map<String, String> tableMap, Item whereItem, List<Field> sourceFields) {
          for (Map.Entry<String, String> entry : tableMap.entrySet()) {
              RowDataPacket row = new RowDataPacket(2);
              String name = entry.getKey();
              if (DbleServer.getInstance().getSystemVariables().isLowerCaseTableNames()) {
                  name = name.toLowerCase();
              }
              row.add(StringUtil.encode(name, shardingService.getCharset().getResults()));
              row.add(StringUtil.encode(entry.getValue(), shardingService.getCharset().getResults()));
              if (whereItem != null) {
                  HandlerTool.initFields(sourceFields, row.fieldValues);
                  /* filter by where condition */
                  if (whereItem.valBool()) {
                      row.setPacketId(shardingService.nextPacketId());
                      buffer = row.write(buffer, shardingService, true);
                  }
              } else {
                  row.setPacketId(shardingService.nextPacketId());
                  buffer = row.write(buffer, shardingService, true);
              }
          }
          PackageBufINf packBuffInfo = new PackageBufINf();
          packBuffInfo.setBuffer(buffer);
          return packBuffInfo;
      }
  
      public static PackageBufINf writeTablesHeaderAndRows(ByteBuffer buffer, ShardingService shardingService, Map<String, String> tableMap, String cSchema) {
          int fieldCount = 1;
          ResultSetHeaderPacket header = PacketUtil.getHeader(fieldCount);
          FieldPacket[] fields = new FieldPacket[fieldCount];
          int i = 0;
          header.setPacketId(shardingService.nextPacketId());
          fields[i] = PacketUtil.getField("Tables_in_" + cSchema, Fields.FIELD_TYPE_VAR_STRING);
          fields[i].setPacketId(shardingService.nextPacketId());
  
          EOFPacket eof = new EOFPacket();
          eof.setPacketId(shardingService.nextPacketId());
          // writeDirectly header
          buffer = header.write(buffer, shardingService, true);
          // writeDirectly fields
          for (FieldPacket field : fields) {
              buffer = field.write(buffer, shardingService, true);
          }
          // writeDirectly eof
          eof.write(buffer, shardingService, true);
          for (String name : tableMap.keySet()) {
              RowDataPacket row = new RowDataPacket(fieldCount);
              if (DbleServer.getInstance().getSystemVariables().isLowerCaseTableNames()) {
                  name = name.toLowerCase();
              }
              row.add(StringUtil.encode(name, shardingService.getCharset().getResults()));
              row.setPacketId(shardingService.nextPacketId());
              buffer = row.write(buffer, shardingService, true);
          }
          PackageBufINf packBuffInfo = new PackageBufINf();
          packBuffInfo.setBuffer(buffer);
          return packBuffInfo;
      }
  
      private static void writeRowEof(ByteBuffer buffer, ShardingService shardingService) {
          // writeDirectly last eof
          EOFRowPacket lastEof = new EOFRowPacket();
          lastEof.setPacketId(shardingService.nextPacketId());
          lastEof.write(buffer, shardingService);
      }
  
      public static Map<String, String> getTableSet(String cSchema, ShowTablesStmtInfo info) {
          //remove the table which is not created but configured
          SchemaMeta schemata = ProxyMeta.getInstance().getTmManager().getCatalogs().get(cSchema);
          if (schemata == null) {
              return new HashMap<>();
          }
          Map tableMeta = schemata.getTableMetas();
          Map<String, ViewMeta> viewMeta = schemata.getViewMetas();
          TreeMap<String, String> tableMap = new TreeMap<>();
          Pattern pattern = null;
          if (null != info.getLike()) {
              String p = "^" + info.getLike().replaceAll("%", ".*");
              pattern = Pattern.compile(p, Pattern.CASE_INSENSITIVE);
          }
          Map<String, SchemaConfig> schemas = DbleServer.getInstance().getConfig().getSchemas();
          for (BaseTableConfig tbConfig : schemas.get(cSchema).getTables().values()) {
              String tbName = tbConfig.getName();
              if (tableMeta.get(tbName) != null && (pattern == null || pattern.matcher(tbName).matches())) {
                  String tbType = "BASE TABLE";
                  if (info.isAll()) {
                      if (tbConfig instanceof ShardingTableConfig) {
                          tbType = "SHARDING TABLE";
                      } else if (tbConfig instanceof GlobalTableConfig) {
                          tbType = "GLOBAL TABLE";
                      } else if (tbConfig instanceof SingleTableConfig) {
                          tbType = "SINGLE TABLE";
                      } else {
                          tbType = "CHILD TABLE";
                      }
                  }
                  tableMap.put(tbName, tbType);
              }
          }
          for (ViewMeta vm : viewMeta.values()) {
              String viewName = vm.getViewName();
              if (viewName != null && (pattern == null || pattern.matcher(viewName).matches())) {
                  tableMap.put(viewName, "VIEW");
              }
          }
          return tableMap;
      }
  
  }
  