#include "os.h"
#include "qTableMeta.h"
#include "qPlan.h"
#include "qExecutor.h"
#include "qUtil.h"
#include "texpr.h"
#include "tscUtil.h"
#include "tsclient.h"

#define QNODE_TAGSCAN       1
#define QNODE_TABLESCAN     2
#define QNODE_PROJECT       3
#define QNODE_AGGREGATE     4
#define QNODE_GROUPBY       5
#define QNODE_LIMIT         6
#define QNODE_JOIN          7
#define QNODE_DISTINCT      8
#define QNODE_SORT          9
#define QNODE_UNIONALL      10
#define QNODE_TIMEWINDOW    11
#define QNODE_SESSIONWINDOW 12
#define QNODE_FILL          13

typedef struct SFillEssInfo {
  int32_t  fillType;  // fill type
  int64_t *val;       // fill value
} SFillEssInfo;

typedef struct SJoinCond {
  bool     tagExists; // denote if tag condition exists or not
  SColumn *tagCond[2];
  SColumn *colCond[2];
} SJoinCond;

static SQueryNode* createQueryNode(int32_t type, const char* name, SQueryNode** prev, int32_t numOfPrev,
                                   SExprInfo** pExpr, int32_t numOfOutput, SQueryTableInfo* pTableInfo,
                                   void* pExtInfo) {
  SQueryNode* pNode = calloc(1, sizeof(SQueryNode));

  pNode->info.type = type;
  pNode->info.name = strdup(name);

  if (pTableInfo->id.uid != 0 && pTableInfo->tableName) { // it is a true table
    pNode->tableInfo.id = pTableInfo->id;
    pNode->tableInfo.tableName = strdup(pTableInfo->tableName);
  }

  pNode->numOfOutput = numOfOutput;
  pNode->pExpr = calloc(numOfOutput, sizeof(SExprInfo));
  for(int32_t i = 0; i < numOfOutput; ++i) {
    tscExprAssign(&pNode->pExpr[i], pExpr[i]);
  }

  pNode->pPrevNodes = taosArrayInit(4, POINTER_BYTES);
  for(int32_t i = 0; i < numOfPrev; ++i) {
    taosArrayPush(pNode->pPrevNodes, &prev[i]);
  }

  switch(type) {
    case QNODE_TABLESCAN: {
      STimeWindow* window = calloc(1, sizeof(STimeWindow));
      memcpy(window, pExtInfo, sizeof(STimeWindow));
      pNode->pExtInfo = window;
      break;
    }

    case QNODE_TIMEWINDOW: {
      SInterval* pInterval = calloc(1, sizeof(SInterval));
      pNode->pExtInfo = pInterval;
      memcpy(pInterval, pExtInfo, sizeof(SInterval));
      break;
    }

    case QNODE_GROUPBY: {
      SGroupbyExpr* p = (SGroupbyExpr*) pExtInfo;
      SGroupbyExpr* pGroupbyExpr = calloc(1, sizeof(SGroupbyExpr));

      pGroupbyExpr->tableIndex = p->tableIndex;
      pGroupbyExpr->orderType  = p->orderType;
      pGroupbyExpr->orderIndex = p->orderIndex;
      pGroupbyExpr->numOfGroupCols = p->numOfGroupCols;
      pGroupbyExpr->columnInfo = taosArrayDup(p->columnInfo);
      pNode->pExtInfo = pGroupbyExpr;
      break;
    }

    case QNODE_FILL: { // todo !!
      pNode->pExtInfo = pExtInfo;
      break;
    }

    case QNODE_LIMIT: {
      pNode->pExtInfo = calloc(1, sizeof(SLimitVal));
      memcpy(pNode->pExtInfo, pExtInfo, sizeof(SLimitVal));
      break;
    }
  }
  return pNode;
}

static SQueryNode* doAddTableColumnNode(SQueryInfo* pQueryInfo, STableMetaInfo* pTableMetaInfo, SQueryTableInfo* info,
                                        SArray* pExprs, SArray* tableCols) {
  if (pQueryInfo->onlyTagQuery) {
    int32_t     num = (int32_t) taosArrayGetSize(pExprs);
    SQueryNode* pNode = createQueryNode(QNODE_TAGSCAN, "TableTagScan", NULL, 0, pExprs->pData, num, info, NULL);

    if (pQueryInfo->distinct) {
      pNode = createQueryNode(QNODE_DISTINCT, "Distinct", &pNode, 1, pExprs->pData, num, info, NULL);
    }

    return pNode;
  }

  STimeWindow* window = &pQueryInfo->window;
  SQueryNode*  pNode = createQueryNode(QNODE_TABLESCAN, "TableScan", NULL, 0, NULL, 0, info, window);

  if (pQueryInfo->projectionQuery) {
    int32_t numOfOutput = (int32_t) taosArrayGetSize(pExprs);
    pNode = createQueryNode(QNODE_PROJECT, "Projection", &pNode, 1, pExprs->pData, numOfOutput, info, NULL);
  } else {
    // table source column projection, generate the projection expr
    int32_t     numOfCols = (int32_t) taosArrayGetSize(tableCols);
    SExprInfo** pExpr = calloc(numOfCols, POINTER_BYTES);
    SSchema*    pSchema = pTableMetaInfo->pTableMeta->schema;

    for (int32_t i = 0; i < numOfCols; ++i) {
      SColumn* pCol = taosArrayGetP(tableCols, i);

      SColumnIndex index = {.tableIndex = 0, .columnIndex = pCol->columnIndex};
      STableMetaInfo* pTableMetaInfo1 = tscGetMetaInfo(pQueryInfo, index.tableIndex);
      SExprInfo*   p = tscExprCreate(pTableMetaInfo1, TSDB_FUNC_PRJ, &index, pCol->info.type, pCol->info.bytes,
                                     pCol->info.colId, 0, TSDB_COL_NORMAL);
      strncpy(p->base.aliasName, pSchema[pCol->columnIndex].name, tListLen(p->base.aliasName));

      pExpr[i] = p;
    }

    pNode = createQueryNode(QNODE_PROJECT, "Projection", &pNode, 1, pExpr, numOfCols, info, NULL);
    for (int32_t i = 0; i < numOfCols; ++i) {
      destroyQueryFuncExpr(pExpr[i], 1);
    }
    tfree(pExpr);
  }

  return pNode;
}

static SQueryNode* doCreateQueryPlanForOneTableImpl(SQueryInfo* pQueryInfo, SQueryNode* pNode, SQueryTableInfo* info,
                                                    SArray* pExprs) {
  // check for aggregation
  if (pQueryInfo->interval.interval > 0) {
    int32_t numOfOutput = (int32_t)taosArrayGetSize(pExprs);

    pNode = createQueryNode(QNODE_TIMEWINDOW, "TimeWindowAgg", &pNode, 1, pExprs->pData, numOfOutput, info,
                            &pQueryInfo->interval);
    if (pQueryInfo->groupbyExpr.numOfGroupCols != 0) {
      pNode = createQueryNode(QNODE_GROUPBY, "Groupby", &pNode, 1, pExprs->pData, numOfOutput, info, &pQueryInfo->groupbyExpr);
    }
  } else if (pQueryInfo->groupbyColumn) {
    int32_t numOfOutput = (int32_t)taosArrayGetSize(pExprs);
    pNode = createQueryNode(QNODE_GROUPBY, "Groupby", &pNode, 1, pExprs->pData, numOfOutput, info,
                            &pQueryInfo->groupbyExpr);
  } else if (pQueryInfo->sessionWindow.gap > 0) {
    pNode = createQueryNode(QNODE_SESSIONWINDOW, "SessionWindowAgg", &pNode, 1, NULL, 0, info, NULL);
  } else if (pQueryInfo->simpleAgg) {
    int32_t numOfOutput = (int32_t)taosArrayGetSize(pExprs);
    pNode = createQueryNode(QNODE_AGGREGATE, "Aggregate", &pNode, 1, pExprs->pData, numOfOutput, info, NULL);
  }

  if (pQueryInfo->havingFieldNum > 0 || pQueryInfo->arithmeticOnAgg) {
    int32_t numOfExpr = (int32_t)taosArrayGetSize(pQueryInfo->exprList1);
    pNode =
        createQueryNode(QNODE_PROJECT, "Projection", &pNode, 1, pQueryInfo->exprList1->pData, numOfExpr, info, NULL);
  }

  if (pQueryInfo->fillType != TSDB_FILL_NONE) {
    SFillEssInfo* pInfo = calloc(1, sizeof(SFillEssInfo));
    pInfo->fillType = pQueryInfo->fillType;
    pInfo->val = calloc(pNode->numOfOutput, sizeof(int64_t));
    memcpy(pInfo->val, pQueryInfo->fillVal, pNode->numOfOutput);

    pNode = createQueryNode(QNODE_FILL, "Fill", &pNode, 1, NULL, 0, info, pInfo);
  }

  if (pQueryInfo->limit.limit != -1 || pQueryInfo->limit.offset != 0) {
    pNode = createQueryNode(QNODE_LIMIT, "Limit", &pNode, 1, NULL, 0, info, &pQueryInfo->limit);
  }

  return pNode;
}

static SQueryNode* doCreateQueryPlanForOneTable(SQueryInfo* pQueryInfo, STableMetaInfo* pTableMetaInfo, SArray* pExprs,
                                                SArray* tableCols) {
  char name[TSDB_TABLE_FNAME_LEN] = {0};
  tNameExtractFullName(&pTableMetaInfo->name, name);

  SQueryTableInfo info = {.tableName = strdup(name), .id = pTableMetaInfo->pTableMeta->id,};

  // handle the only tag query
  SQueryNode* pNode = doAddTableColumnNode(pQueryInfo, pTableMetaInfo, &info, pExprs, tableCols);
  if (pQueryInfo->onlyTagQuery) {
    tfree(info.tableName);
    return pNode;
  }

  SQueryNode* pNode1 = doCreateQueryPlanForOneTableImpl(pQueryInfo, pNode, &info, pExprs);
  tfree(info.tableName);
  return pNode1;
}

SArray* createQueryPlanImpl(SQueryInfo* pQueryInfo) {
  SArray* upstream = NULL;

  if (pQueryInfo->pUpstream != NULL && taosArrayGetSize(pQueryInfo->pUpstream) > 0) {  // subquery in the from clause
    upstream = taosArrayInit(4, POINTER_BYTES);

    size_t size = taosArrayGetSize(pQueryInfo->pUpstream);
    for(int32_t i = 0; i < size; ++i) {
      SQueryInfo* pq = taosArrayGet(pQueryInfo->pUpstream, i);
      SArray* p = createQueryPlanImpl(pq);
      taosArrayAddBatch(upstream, p->pData, (int32_t) taosArrayGetSize(p));
    }
  }

  if (pQueryInfo->numOfTables > 1) {  // it is a join query
    // 1. separate the select clause according to table
    taosArrayDestroy(upstream);
    upstream = taosArrayInit(5, POINTER_BYTES);

    for(int32_t i = 0; i < pQueryInfo->numOfTables; ++i) {
      STableMetaInfo* pTableMetaInfo = pQueryInfo->pTableMetaInfo[i];
      uint64_t        uid = pTableMetaInfo->pTableMeta->id.uid;

      SArray* exprList = taosArrayInit(4, POINTER_BYTES);
      if (tscExprCopy(exprList, pQueryInfo->exprList, uid, true) != 0) {
        terrno = TSDB_CODE_TSC_OUT_OF_MEMORY;
        tscExprDestroy(exprList);
        exit(-1);
      }

      // 2. create the query execution node
      char name[TSDB_TABLE_FNAME_LEN] = {0};
      tNameExtractFullName(&pTableMetaInfo->name, name);
      SQueryTableInfo info = {.tableName = strdup(name), .id = pTableMetaInfo->pTableMeta->id,};

      // 3. get the required table column list
      SArray* tableColumnList = taosArrayInit(4, sizeof(SColumn));
      tscColumnListCopy(tableColumnList, pQueryInfo->colList, uid);

      // 4. add the projection query node
      SQueryNode* pNode = doAddTableColumnNode(pQueryInfo, pTableMetaInfo, &info, exprList, tableColumnList);
      tscColumnListDestroy(tableColumnList);
      tscExprDestroy(exprList);
      taosArrayPush(upstream, &pNode);
    }

    // 3. add the join node here
    SQueryTableInfo info = {0};
    int32_t num = (int32_t) taosArrayGetSize(pQueryInfo->exprList);
    SQueryNode* pNode = createQueryNode(QNODE_JOIN, "Join", upstream->pData, pQueryInfo->numOfTables,
        pQueryInfo->exprList->pData, num, &info, NULL);

    // 4. add the aggregation or projection execution node
    pNode = doCreateQueryPlanForOneTableImpl(pQueryInfo, pNode, &info, pQueryInfo->exprList);
    upstream = taosArrayInit(5, POINTER_BYTES);
    taosArrayPush(upstream, &pNode);
  } else { // only one table, normal query process
    STableMetaInfo* pTableMetaInfo = pQueryInfo->pTableMetaInfo[0];
    SQueryNode* pNode = doCreateQueryPlanForOneTable(pQueryInfo, pTableMetaInfo, pQueryInfo->exprList, pQueryInfo->colList);
    upstream = taosArrayInit(5, POINTER_BYTES);
    taosArrayPush(upstream, &pNode);
  }

  return upstream;
}

SQueryNode* qCreateQueryPlan(SQueryInfo* pQueryInfo) {
  SArray* upstream = createQueryPlanImpl(pQueryInfo);
  assert(taosArrayGetSize(upstream) == 1);

  SQueryNode* p = taosArrayGetP(upstream, 0);
  taosArrayDestroy(upstream);

  return p;
}

static void doDestroyQueryNode(SQueryNode* pQueryNode) {
  tfree(pQueryNode->pExtInfo);
  tfree(pQueryNode->pSchema);
  tfree(pQueryNode->info.name);

  tfree(pQueryNode->tableInfo.tableName);

  pQueryNode->pExpr = destroyQueryFuncExpr(pQueryNode->pExpr, pQueryNode->numOfOutput);

  if (pQueryNode->pPrevNodes != NULL) {
    int32_t size = (int32_t) taosArrayGetSize(pQueryNode->pPrevNodes);
    for(int32_t i = 0; i < size; ++i) {
      SQueryNode* p = taosArrayGetP(pQueryNode->pPrevNodes, i);
      doDestroyQueryNode(p);
    }

    taosArrayDestroy(pQueryNode->pPrevNodes);
  }

  tfree(pQueryNode);
}

void* qDestroyQueryPlan(SQueryNode* pQueryNode) {
  if (pQueryNode == NULL) {
    return NULL;
  }

  doDestroyQueryNode(pQueryNode);
  return NULL;
}

bool hasAliasName(SExprInfo* pExpr) {
  assert(pExpr != NULL);
  return strncmp(pExpr->base.token, pExpr->base.aliasName, tListLen(pExpr->base.aliasName)) != 0;
}

static int32_t doPrintPlan(char* buf, SQueryNode* pQueryNode, int32_t level, int32_t totalLen) {
  if (level > 0) {
    sprintf(buf + totalLen, "%*c", level, ' ');
    totalLen += level;
  }

  int32_t len1 = sprintf(buf + totalLen, "%s(", pQueryNode->info.name);
  int32_t len = len1 + totalLen;

  switch(pQueryNode->info.type) {
    case QNODE_TABLESCAN: {
      STimeWindow* win = (STimeWindow*)pQueryNode->pExtInfo;
      len1 = sprintf(buf + len, "%s #%" PRIu64 ") time_range: %" PRId64 " - %" PRId64 "\n",
                     pQueryNode->tableInfo.tableName, pQueryNode->tableInfo.id.uid, win->skey, win->ekey);
      len += len1;
      break;
    }

    case QNODE_PROJECT: {
      len1 = sprintf(buf + len, "cols: ");
      len += len1;

      for(int32_t i = 0; i < pQueryNode->numOfOutput; ++i) {
        SSqlExpr* p = &pQueryNode->pExpr[i].base;
        len1 = sprintf(buf + len, "[%s #%d]", p->aliasName, p->resColId);
        len += len1;

        if (i < pQueryNode->numOfOutput - 1) {
          len1 = sprintf(buf + len, ", ");
          len += len1;
        }
      }

      len1 = sprintf(buf + len, ")");
      len += len1;

      //todo print filter info
      len1 = sprintf(buf + len, " filters:(nil)\n");
      len += len1;
      break;
    }

    case QNODE_AGGREGATE: {
      for(int32_t i = 0; i < pQueryNode->numOfOutput; ++i) {
        SSqlExpr* pExpr = &pQueryNode->pExpr[i].base;
        if (hasAliasName(&pQueryNode->pExpr[i])) {
          len1 = sprintf(buf + len,"[%s #%s]", pExpr->token, pExpr->aliasName);
        } else {
          len1 = sprintf(buf + len,"[%s]", pExpr->token);
        }

        len += len1;
        if (i < pQueryNode->numOfOutput - 1) {
          len1 = sprintf(buf + len, ", ");
          len += len1;
        }
      }

      len1 = sprintf(buf + len, ")\n");
      len += len1;
      break;
    }

    case QNODE_TIMEWINDOW: {
      for(int32_t i = 0; i < pQueryNode->numOfOutput; ++i) {
        SSqlExpr* pExpr = &pQueryNode->pExpr[i].base;
        if (hasAliasName(&pQueryNode->pExpr[i])) {
          len1 = sprintf(buf + len,"[%s #%s]", pExpr->token, pExpr->aliasName);
        } else {
          len1 = sprintf(buf + len,"[%s]", pExpr->token);
        }

        len += len1;
        if (i < pQueryNode->numOfOutput - 1) {
          len1 = sprintf(buf + len,", ");
          len += len1;
        }
      }

      len1 = sprintf(buf + len,") ");
      len += len1;

      SInterval* pInterval = pQueryNode->pExtInfo;
      len1 = sprintf(buf + len, "interval:%" PRId64 "(%s), sliding:%" PRId64 "(%s), offset:%" PRId64 "\n",
                     pInterval->interval, TSDB_TIME_PRECISION_MILLI_STR, pInterval->sliding, TSDB_TIME_PRECISION_MILLI_STR,
                     pInterval->offset);
      len += len1;

      break;
    }

    case QNODE_GROUPBY: {  // todo hide the invisible column
      for(int32_t i = 0; i < pQueryNode->numOfOutput; ++i) {
        SSqlExpr* pExpr = &pQueryNode->pExpr[i].base;

        if (hasAliasName(&pQueryNode->pExpr[i])) {
          len1 = sprintf(buf + len,"[%s #%s]", pExpr->token, pExpr->aliasName);
        } else {
          len1 = sprintf(buf + len,"[%s]", pExpr->token);
        }

        len += len1;
        if (i < pQueryNode->numOfOutput - 1) {
          len1 = sprintf(buf + len,", ");
          len += len1;
        }
      }

      SGroupbyExpr* pGroupbyExpr = pQueryNode->pExtInfo;
      SColIndex* pIndex = taosArrayGet(pGroupbyExpr->columnInfo, 0);

      len1 = sprintf(buf + len,") groupby_col: [%s #%d]\n", pIndex->name, pIndex->colId);
      len += len1;

      break;
    }

    case QNODE_FILL: {
      SFillEssInfo* pEssInfo = pQueryNode->pExtInfo;
      len1 = sprintf(buf + len,"%d", pEssInfo->fillType);
      len += len1;

      if (pEssInfo->fillType == TSDB_FILL_SET_VALUE) {
        len1 = sprintf(buf + len,", val:");
        len += len1;

        // todo get the correct fill data type
        for(int32_t i = 0; i < pQueryNode->numOfOutput; ++i) {
          len1 = sprintf(buf + len,"%"PRId64, pEssInfo->val[i]);
          len += len1;

          if (i < pQueryNode->numOfOutput - 1) {
            len1 = sprintf(buf + len,", ");
            len += len1;
          }
        }
      }

      len1 = sprintf(buf + len,")\n");
      len += len1;
      break;
    }

    case QNODE_LIMIT: {
      SLimitVal* pVal = pQueryNode->pExtInfo;
      len1 = sprintf(buf + len,"limit: %"PRId64", offset: %"PRId64")\n", pVal->limit, pVal->offset);
      len += len1;
      break;
    }

    case QNODE_DISTINCT:
    case QNODE_TAGSCAN: {
      len1 = sprintf(buf + len,"cols: ");
      len += len1;

      for(int32_t i = 0; i < pQueryNode->numOfOutput; ++i) {
        SSqlExpr* p = &pQueryNode->pExpr[i].base;
        len1 = sprintf(buf + len,"[%s #%d]", p->aliasName, p->resColId);
        len += len1;

        if (i < pQueryNode->numOfOutput - 1) {
          len1 = sprintf(buf + len,", ");
          len += len1;
        }
      }

      len1 = sprintf(buf + len,")\n");
      len += len1;

      break;
    }

    case QNODE_JOIN: {
      //  print join condition
      len1 = sprintf(buf + len, ")\n");
      len += len1;
      break;
    }
  }

  return len;
}

int32_t queryPlanToStringImpl(char* buf, SQueryNode* pQueryNode, int32_t level, int32_t totalLen) {
  int32_t len = doPrintPlan(buf, pQueryNode, level, totalLen);

  for(int32_t i = 0; i < taosArrayGetSize(pQueryNode->pPrevNodes); ++i) {
    SQueryNode* p1 = taosArrayGetP(pQueryNode->pPrevNodes, i);
    int32_t len1 = queryPlanToStringImpl(buf, p1, level + 1, len);
    len = len1;
  }

  return len;
}

char* queryPlanToString(SQueryNode* pQueryNode) {
  assert(pQueryNode);

  char* buf = calloc(1, 4096);

  int32_t len = sprintf(buf, "===== logic plan =====\n");
  queryPlanToStringImpl(buf, pQueryNode, 0, len);
  return buf;
}

SQueryNode* queryPlanFromString() {
  return NULL;
}

SArray* createTableScanPlan(SQueryAttr* pQueryAttr) {
  SArray* plan = taosArrayInit(4, sizeof(int32_t));

  int32_t op = 0;
  if (onlyQueryTags(pQueryAttr)) {
//    op = OP_TagScan;
  } else {
    if (pQueryAttr->queryBlockDist) {
      op = OP_TableBlockInfoScan;
    } else if (pQueryAttr->tsCompQuery || pQueryAttr->pointInterpQuery || pQueryAttr->diffQuery) {
      op = OP_TableSeqScan;
    } else if (pQueryAttr->needReverseScan) {
      op = OP_DataBlocksOptScan;
    } else {
      op = OP_TableScan;
    }

    taosArrayPush(plan, &op);
  }

  return plan;
}

SArray* createExecOperatorPlan(SQueryAttr* pQueryAttr) {
  SArray* plan = taosArrayInit(4, sizeof(int32_t));
  int32_t op = 0;

  if (onlyQueryTags(pQueryAttr)) {  // do nothing for tags query
    op = OP_TagScan;
    taosArrayPush(plan, &op);

    if (pQueryAttr->distinct) {
      op = OP_Distinct;
      taosArrayPush(plan, &op);
    }
  } else if (pQueryAttr->interval.interval > 0) {
    if (pQueryAttr->stableQuery) {
      if (pQueryAttr->pointInterpQuery) {
        op = OP_AllMultiTableTimeInterval;
      } else {
        op = OP_MultiTableTimeInterval;
      }
      taosArrayPush(plan, &op);
    } else {      
      if (pQueryAttr->pointInterpQuery) {
        op = OP_AllTimeWindow;
      } else {
        op = OP_TimeWindow;
      }
      taosArrayPush(plan, &op);

      if (pQueryAttr->pExpr2 != NULL) {
        op = OP_Project;
        taosArrayPush(plan, &op);
      }

      if (pQueryAttr->fillType != TSDB_FILL_NONE) {
        op = OP_Fill;
        taosArrayPush(plan, &op);
      }
    }

  } else if (pQueryAttr->groupbyColumn) {
    op = OP_Groupby;
    taosArrayPush(plan, &op);

    if (!pQueryAttr->stableQuery && pQueryAttr->havingNum > 0) {
      op = OP_Filter;
      taosArrayPush(plan, &op);
    }

    if (pQueryAttr->pExpr2 != NULL) {
      op = OP_Project;
      taosArrayPush(plan, &op);
    }
  } else if (pQueryAttr->sw.gap > 0) {
    op = OP_SessionWindow;
    taosArrayPush(plan, &op);

    if (pQueryAttr->pExpr2 != NULL) {
      op = OP_Project;
      taosArrayPush(plan, &op);
    }
  } else if (pQueryAttr->stateWindow) {
    op =  OP_StateWindow;
    taosArrayPush(plan, &op);

    if (pQueryAttr->pExpr2 != NULL) {
      op = OP_Project;
      taosArrayPush(plan, &op);
    }
  } else if (pQueryAttr->simpleAgg) {
    if (pQueryAttr->stableQuery && !pQueryAttr->tsCompQuery && !pQueryAttr->diffQuery) {
      op = OP_MultiTableAggregate;
    } else {
      op = OP_Aggregate;
    }

    taosArrayPush(plan, &op);

    if (!pQueryAttr->stableQuery && pQueryAttr->havingNum > 0) {
      op = OP_Filter;
      taosArrayPush(plan, &op);
    }

    if (pQueryAttr->pExpr2 != NULL && !pQueryAttr->stableQuery) {
      op = OP_Project;
      taosArrayPush(plan, &op);
    }
  } else {  // diff/add/multiply/subtract/division
    if (pQueryAttr->numOfFilterCols > 0 && pQueryAttr->createFilterOperator && pQueryAttr->vgId == 0) { // todo refactor
      op = OP_Filter;
      taosArrayPush(plan, &op);
    } else {
      op = OP_Project;
      taosArrayPush(plan, &op);

      if (pQueryAttr->pExpr2 != NULL) {
        op = OP_Project;
        taosArrayPush(plan, &op);
      }

      if (pQueryAttr->distinct) {
        op = OP_Distinct;
        taosArrayPush(plan, &op);
      }
    }

    // outer query order by support
    int32_t orderColId = pQueryAttr->order.orderColId;
    if (pQueryAttr->vgId == 0 && orderColId != PRIMARYKEY_TIMESTAMP_COL_INDEX && orderColId != INT32_MIN) {
      op = OP_Order;
      taosArrayPush(plan, &op);
    }
  }

  if (pQueryAttr->limit.limit > 0 || pQueryAttr->limit.offset > 0) {
    op = OP_Limit;
    taosArrayPush(plan, &op);
  }

  return plan;
}

SArray* createGlobalMergePlan(SQueryAttr* pQueryAttr) {
  SArray* plan = taosArrayInit(4, sizeof(int32_t));

  if (!pQueryAttr->stableQuery) {
    return plan;
  }

  int32_t op = OP_MultiwayMergeSort;
  taosArrayPush(plan, &op);

  if (pQueryAttr->distinct) {
    op = OP_Distinct;
    taosArrayPush(plan, &op);
  }

  if (pQueryAttr->simpleAgg || (pQueryAttr->interval.interval > 0 || pQueryAttr->sw.gap > 0)) {
    op = OP_GlobalAggregate;
    taosArrayPush(plan, &op);

    if (pQueryAttr->havingNum > 0) {
      op = OP_Filter;
      taosArrayPush(plan, &op);
    }

    if (pQueryAttr->pExpr2 != NULL) {
      op = OP_Project;
      taosArrayPush(plan, &op);
    }
  }

  // fill operator
  if (pQueryAttr->fillType != TSDB_FILL_NONE && pQueryAttr->interval.interval > 0) {
    op = OP_Fill;
    taosArrayPush(plan, &op);
  }

  // limit/offset operator
  if (pQueryAttr->limit.limit > 0 || pQueryAttr->limit.offset > 0 ||
      pQueryAttr->slimit.limit > 0 || pQueryAttr->slimit.offset > 0) {
    op = OP_SLimit;
    taosArrayPush(plan, &op);
  }

  return plan;
}
