未验证 提交 27817adb 编写于 作者: H Haojun Liao 提交者: GitHub

Merge pull request #11199 from taosdata/feature/3.0_liaohj

[td-14393] refactor and support filter condition in where clause.
......@@ -113,6 +113,20 @@ static FORCE_INLINE void colDataAppendNULL(SColumnInfoData* pColumnInfoData, uin
pColumnInfoData->hasNull = true;
}
static FORCE_INLINE void colDataAppendNNULL(SColumnInfoData* pColumnInfoData, uint32_t start, size_t nRows) {
if (IS_VAR_DATA_TYPE(pColumnInfoData->info.type)) {
for(int32_t i = start; i < start + nRows; ++i) {
pColumnInfoData->varmeta.offset[i] = -1; // it is a null value of VAR type.
}
} else {
for(int32_t i = start; i < start + nRows; ++i) {
colDataSetNull_f(pColumnInfoData->nullbitmap, i);
}
}
pColumnInfoData->hasNull = true;
}
static FORCE_INLINE int32_t colDataAppendInt8(SColumnInfoData* pColumnInfoData, uint32_t currentRow, int8_t* v) {
ASSERT(pColumnInfoData->info.type == TSDB_DATA_TYPE_TINYINT ||
pColumnInfoData->info.type == TSDB_DATA_TYPE_UTINYINT || pColumnInfoData->info.type == TSDB_DATA_TYPE_BOOL);
......
......@@ -3026,11 +3026,27 @@ int32_t loadDataBlock(SExecTaskInfo* pTaskInfo, STableScanInfo* pTableScanInfo,
int8_t* rowRes = NULL;
bool keep = filterExecute(filter, pBlock, &rowRes, NULL, param1.numOfCols);
// filterSetColFieldData(pQueryAttr->pFilters, pBlock->info.numOfCols, pBlock->pDataBlock);
SSDataBlock* px = createOneDataBlock(pBlock);
blockDataEnsureCapacity(px, pBlock->info.rows);
// if (pQueryAttr->pFilters != NULL) {
// filterColRowsInDataBlock(pRuntimeEnv, pBlock, ascQuery);
// }
int32_t numOfRow = 0;
for (int32_t i = 0; i < pBlock->info.numOfCols; ++i) {
SColumnInfoData* pDst = taosArrayGet(px->pDataBlock, i);
SColumnInfoData* pSrc = taosArrayGet(pBlock->pDataBlock, i);
numOfRow = 0;
for (int32_t j = 0; j < pBlock->info.rows; ++j) {
if (rowRes[j] == 0) {
continue;
}
colDataAppend(pDst, numOfRow, colDataGetData(pSrc, j), false);
numOfRow += 1;
}
*pSrc = *pDst;
}
pBlock->info.rows = numOfRow;
}
return TSDB_CODE_SUCCESS;
......
......@@ -229,7 +229,7 @@ typedef struct SFltBuildGroupCtx {
int32_t code;
} SFltBuildGroupCtx;
typedef struct SFilterInfo {
struct SFilterInfo {
bool scalarMode;
SFltScalarCtx sclCtx;
uint32_t options;
......@@ -254,7 +254,7 @@ typedef struct SFilterInfo {
SArray *blkList;
SFilterPCtx pctx;
} SFilterInfo;
};
#define FILTER_NO_MERGE_DATA_TYPE(t) ((t) == TSDB_DATA_TYPE_BINARY || (t) == TSDB_DATA_TYPE_NCHAR || (t) == TSDB_DATA_TYPE_JSON)
#define FILTER_NO_MERGE_OPTR(o) ((o) == OP_TYPE_IS_NULL || (o) == OP_TYPE_IS_NOT_NULL || (o) == FILTER_DUMMY_EMPTY_OPTR)
......
......@@ -3638,16 +3638,16 @@ int32_t filterInitFromNode(SNode* pNode, SFilterInfo **pInfo, uint32_t options)
info = *pInfo;
info->options = options;
SFltTreeStat stat = {0};
FLT_ERR_JRET(fltReviseNodes(info, &pNode, &stat));
SFltTreeStat stat1 = {0};
FLT_ERR_JRET(fltReviseNodes(info, &pNode, &stat1));
info->scalarMode = stat.scalarMode;
info->scalarMode = stat1.scalarMode;
if (!info->scalarMode) {
FLT_ERR_JRET(fltInitFromNode(pNode, info, options));
} else {
info->sclCtx.node = pNode;
FLT_ERR_JRET(fltOptimizeNodes(info, &info->sclCtx.node, &stat));
FLT_ERR_JRET(fltOptimizeNodes(info, &info->sclCtx.node, &stat1));
}
return code;
......@@ -3664,24 +3664,16 @@ _return:
bool filterExecute(SFilterInfo *info, SSDataBlock *pSrc, int8_t** p, SColumnDataAgg *statis, int16_t numOfCols) {
if (info->scalarMode) {
SScalarParam output = {0};
SDataType type = {.type = TSDB_DATA_TYPE_BOOL, .bytes = sizeof(bool)};
output.columnData = createColumnInfoData(&type, pSrc->info.rows);
*p = (int8_t *)output.columnData->pData;
SArray *pList = taosArrayInit(1, POINTER_BYTES);
taosArrayPush(pList, &pSrc);
FLT_ERR_RET(scalarCalculate(info->sclCtx.node, pList, &output));
taosArrayDestroy(pList);
// TODO Fix it
// *p = output.orig.data;
// output.orig.data = NULL;
//
// sclFreeParam(&output);
//
// int8_t *r = output.data;
// for (int32_t i = 0; i < output.num; ++i) {
// if (0 == *(r+i)) {
// return false;
// }
// }
return true;
}
......
......@@ -163,7 +163,7 @@ int32_t sclInitParam(SNode* node, SScalarParam *param, SScalarCtx *ctx, int32_t
param->numOfRows = 1;
param->columnData = createColumnInfoData(&valueNode->node.resType, 1);
if (TSDB_DATA_TYPE_NULL == valueNode->node.resType.type) {
colDataAppend(param->columnData, 0, NULL, true);
colDataAppendNULL(param->columnData, 0);
} else {
colDataAppend(param->columnData, 0, nodesGetValueFromNode(valueNode), false);
}
......@@ -311,12 +311,10 @@ int32_t sclExecFunction(SFunctionNode *node, SScalarCtx *ctx, SScalarParam *outp
SCL_ERR_JRET(TSDB_CODE_QRY_OUT_OF_MEMORY);
}
// for (int32_t i = 0; i < rowNum; ++i) {
code = (*ffpSet.process)(params, node->pParameterList->length, output);
if (code) {
sclError("scalar function exec failed, funcId:%d, code:%s", node->funcId, tstrerror(code));
SCL_ERR_JRET(code);
// }
code = (*ffpSet.process)(params, node->pParameterList->length, output);
if (code) {
sclError("scalar function exec failed, funcId:%d, code:%s", node->funcId, tstrerror(code));
SCL_ERR_JRET(code);
}
_return:
......@@ -447,7 +445,6 @@ EDealRes sclRewriteFunction(SNode** pNode, SScalarCtx *ctx) {
*pNode = (SNode*)res;
sclFreeParam(&output);
return DEAL_RES_CONTINUE;
}
......@@ -689,8 +686,9 @@ int32_t scalarCalculate(SNode *pNode, SArray *pBlockList, SScalarParam *pDst) {
int32_t code = 0;
SScalarCtx ctx = {.code = 0, .pBlockList = pBlockList};
// TODO: OPT performance
ctx.pRes = taosHashInit(SCL_DEFAULT_OP_NUM, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BINARY), false, HASH_NO_LOCK);
ctx.pRes = taosHashInit(SCL_DEFAULT_OP_NUM, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BIGINT), false, HASH_NO_LOCK);
if (NULL == ctx.pRes) {
sclError("taosHashInit failed, num:%d", SCL_DEFAULT_OP_NUM);
SCL_ERR_RET(TSDB_CODE_QRY_OUT_OF_MEMORY);
......
......@@ -158,23 +158,23 @@ _getValueAddr_fn_t getVectorValueAddrFn(int32_t srcType) {
static FORCE_INLINE void varToSigned(char *buf, SScalarParam* pOut, int32_t rowIndex) {
int64_t value = strtoll(buf, NULL, 10);
colDataAppend(pOut->columnData, rowIndex, (char*) &value, false);
colDataAppendInt64(pOut->columnData, rowIndex, &value);
}
static FORCE_INLINE void varToUnsigned(char *buf, SScalarParam* pOut, int32_t rowIndex) {
uint64_t value = strtoull(buf, NULL, 10);
colDataAppend(pOut->columnData, rowIndex, (char*) &value, false);
colDataAppendInt64(pOut->columnData, rowIndex, (int64_t*) &value);
}
static FORCE_INLINE void varToFloat(char *buf, SScalarParam* pOut, int32_t rowIndex) {
double value = strtod(buf, NULL);
colDataAppend(pOut->columnData, rowIndex, (char*) &value, false);
colDataAppendDouble(pOut->columnData, rowIndex, &value);
}
static FORCE_INLINE void varToBool(char *buf, SScalarParam* pOut, int32_t rowIndex) {
int64_t value = strtoll(buf, NULL, 10);
bool v = (value != 0)? true:false;
colDataAppend(pOut->columnData, rowIndex, (char*) &v, false);
colDataAppendInt8(pOut->columnData, rowIndex, (int8_t*) &v);
}
int32_t vectorConvertFromVarData(const SScalarParam* pIn, SScalarParam* pOut, int32_t inType, int32_t outType) {
......@@ -198,7 +198,7 @@ int32_t vectorConvertFromVarData(const SScalarParam* pIn, SScalarParam* pOut, in
pOut->numOfRows = pIn->numOfRows;
for (int32_t i = 0; i < pIn->numOfRows; ++i) {
if (colDataIsNull(pIn->columnData, pIn->numOfRows, i, NULL)) {
colDataAppend(pOut->columnData, i, NULL, true);
colDataAppendNULL(pOut->columnData, i);
continue;
}
......@@ -242,13 +242,13 @@ int32_t vectorConvertImpl(const SScalarParam* pIn, SScalarParam* pOut) {
case TSDB_DATA_TYPE_BOOL: {
for (int32_t i = 0; i < pIn->numOfRows; ++i) {
if (colDataIsNull_f(pInputCol->nullbitmap, i)) {
colDataAppend(pOutputCol, i, NULL, true);
colDataAppendNULL(pOutputCol, i);
continue;
}
bool value = 0;
GET_TYPED_DATA(value, int64_t, inType, colDataGetData(pInputCol, i));
colDataAppend(pOutputCol, i, (char*) &value, false);
colDataAppendInt8(pOutputCol, i, (int8_t*) &value);
}
break;
}
......@@ -259,13 +259,13 @@ int32_t vectorConvertImpl(const SScalarParam* pIn, SScalarParam* pOut) {
case TSDB_DATA_TYPE_TIMESTAMP: {
for (int32_t i = 0; i < pIn->numOfRows; ++i) {
if (colDataIsNull_f(pInputCol->nullbitmap, i)) {
colDataAppend(pOutputCol, i, NULL, true);
colDataAppendNULL(pOutputCol, i);
continue;
}
int64_t value = 0;
GET_TYPED_DATA(value, int64_t, inType, colDataGetData(pInputCol, i));
colDataAppend(pOutputCol, i, (char *)&value, false);
colDataAppendInt64(pOutputCol, i, &value);
}
break;
}
......@@ -275,26 +275,26 @@ int32_t vectorConvertImpl(const SScalarParam* pIn, SScalarParam* pOut) {
case TSDB_DATA_TYPE_UBIGINT:
for (int32_t i = 0; i < pIn->numOfRows; ++i) {
if (colDataIsNull_f(pInputCol->nullbitmap, i)) {
colDataAppend(pOutputCol, i, NULL, true);
colDataAppendNULL(pOutputCol, i);
continue;
}
uint64_t value = 0;
GET_TYPED_DATA(value, uint64_t, inType, colDataGetData(pInputCol, i));
colDataAppend(pOutputCol, i, (char*) &value, false);
colDataAppendInt64(pOutputCol, i, (int64_t*)&value);
}
break;
case TSDB_DATA_TYPE_FLOAT:
case TSDB_DATA_TYPE_DOUBLE:
for (int32_t i = 0; i < pIn->numOfRows; ++i) {
if (colDataIsNull_f(pInputCol->nullbitmap, i)) {
colDataAppend(pOutputCol, i, NULL, true);
colDataAppendNULL(pOutputCol, i);
continue;
}
double value = 0;
GET_TYPED_DATA(value, double, inType, colDataGetData(pInputCol, i));
colDataAppend(pOutputCol, i, (char*) &value, false);
colDataAppendDouble(pOutputCol, i, &value);
}
break;
default:
......@@ -445,7 +445,7 @@ static void vectorMathAddHelper(SColumnInfoData* pLeftCol, SColumnInfoData* pRig
double *output = (double *)pOutputCol->pData;
if (colDataIsNull_f(pRightCol->nullbitmap, 0)) { // Set pLeft->numOfRows NULL value
// TODO set numOfRows NULL value
colDataAppendNNULL(pOutputCol, 0, numOfRows);
} else {
for (; i >= 0 && i < numOfRows; i += step, output += 1) {
*output = getVectorDoubleValueFnLeft(pLeftCol->pData, i) + getVectorDoubleValueFnRight(pRightCol->pData, 0);
......@@ -527,7 +527,7 @@ static void vectorMathSubHelper(SColumnInfoData* pLeftCol, SColumnInfoData* pRig
double *output = (double *)pOutputCol->pData;
if (colDataIsNull_f(pRightCol->nullbitmap, 0)) { // Set pLeft->numOfRows NULL value
// TODO set numOfRows NULL value
colDataAppendNNULL(pOutputCol, 0, numOfRows);
} else {
for (; i >= 0 && i < numOfRows; i += step, output += 1) {
*output = (getVectorDoubleValueFnLeft(pLeftCol->pData, i) - getVectorDoubleValueFnRight(pRightCol->pData, 0)) * factor;
......@@ -586,7 +586,7 @@ static void vectorMathMultiplyHelper(SColumnInfoData* pLeftCol, SColumnInfoData*
double *output = (double *)pOutputCol->pData;
if (colDataIsNull_f(pRightCol->nullbitmap, 0)) { // Set pLeft->numOfRows NULL value
// TODO set numOfRows NULL value
colDataAppendNNULL(pOutputCol, 0, numOfRows);
} else {
for (; i >= 0 && i < numOfRows; i += step, output += 1) {
*output = getVectorDoubleValueFnLeft(pLeftCol->pData, i) * getVectorDoubleValueFnRight(pRightCol->pData, 0);
......@@ -666,7 +666,7 @@ void vectorMathDivide(SScalarParam* pLeft, SScalarParam* pRight, SScalarParam *p
} else if (pLeft->numOfRows == 1) {
if (colDataIsNull_f(pLeftCol->nullbitmap, 0)) { // Set pLeft->numOfRows NULL value
// TODO set numOfRows NULL value
colDataAppendNNULL(pOutputCol, 0, pRight->numOfRows);
} else {
for (; i >= 0 && i < pRight->numOfRows; i += step, output += 1) {
*output = getVectorDoubleValueFnLeft(pLeftCol->pData, 0) / getVectorDoubleValueFnRight(pRightCol->pData, i);
......@@ -678,7 +678,7 @@ void vectorMathDivide(SScalarParam* pLeft, SScalarParam* pRight, SScalarParam *p
}
} else if (pRight->numOfRows == 1) {
if (colDataIsNull_f(pRightCol->nullbitmap, 0)) { // Set pLeft->numOfRows NULL value
// TODO set numOfRows NULL value
colDataAppendNNULL(pOutputCol, 0, pLeft->numOfRows);
} else {
for (; i >= 0 && i < pLeft->numOfRows; i += step, output += 1) {
*output = getVectorDoubleValueFnLeft(pLeftCol->pData, i) / getVectorDoubleValueFnRight(pRightCol->pData, 0);
......@@ -714,14 +714,14 @@ void vectorMathRemainder(SScalarParam* pLeft, SScalarParam* pRight, SScalarParam
if (pLeft->numOfRows == pRight->numOfRows) {
for (; i < pRight->numOfRows && i >= 0; i += step, output += 1) {
if (colDataIsNull_f(pLeftCol->nullbitmap, i) || colDataIsNull_f(pRightCol->nullbitmap, i)) {
colDataAppend(pOutputCol, i, NULL, true);
colDataAppendNULL(pOutputCol, i);
continue;
}
double lx = getVectorDoubleValueFnLeft(pLeftCol->pData, i);
double rx = getVectorDoubleValueFnRight(pRightCol->pData, i);
if (isnan(lx) || isinf(lx) || isnan(rx) || isinf(rx)) {
colDataAppend(pOutputCol, i, NULL, true);
colDataAppendNULL(pOutputCol, i);
continue;
}
......@@ -730,17 +730,17 @@ void vectorMathRemainder(SScalarParam* pLeft, SScalarParam* pRight, SScalarParam
} else if (pLeft->numOfRows == 1) {
double lx = getVectorDoubleValueFnLeft(pLeftCol->pData, 0);
if (colDataIsNull_f(pLeftCol->nullbitmap, 0) || isnan(lx) || isinf(lx)) { // Set pLeft->numOfRows NULL value
// TODO set numOfRows NULL value
colDataAppendNNULL(pOutputCol, 0, pRight->numOfRows);
} else {
for (; i >= 0 && i < pRight->numOfRows; i += step, output += 1) {
if (colDataIsNull_f(pRightCol->nullbitmap, i)) {
colDataAppend(pOutputCol, i, NULL, true);
colDataAppendNULL(pOutputCol, i);
continue;
}
double rx = getVectorDoubleValueFnRight(pRightCol->pData, i);
if (isnan(rx) || isinf(rx) || FLT_EQUAL(rx, 0)) {
colDataAppend(pOutputCol, i, NULL, true);
colDataAppendNULL(pOutputCol, i);
continue;
}
......@@ -750,17 +750,17 @@ void vectorMathRemainder(SScalarParam* pLeft, SScalarParam* pRight, SScalarParam
} else if (pRight->numOfRows == 1) {
double rx = getVectorDoubleValueFnRight(pRightCol->pData, 0);
if (colDataIsNull_f(pRightCol->nullbitmap, 0) || FLT_EQUAL(rx, 0)) { // Set pLeft->numOfRows NULL value
// TODO set numOfRows NULL value
colDataAppendNNULL(pOutputCol, 0, pLeft->numOfRows);
} else {
for (; i >= 0 && i < pLeft->numOfRows; i += step, output += 1) {
if (colDataIsNull_f(pLeftCol->nullbitmap, i)) {
colDataAppend(pOutputCol, i, NULL, true);
colDataAppendNULL(pOutputCol, i);
continue;
}
double lx = getVectorDoubleValueFnLeft(pLeftCol->pData, i);
if (isnan(lx) || isinf(lx)) {
colDataAppend(pOutputCol, i, NULL, true);
colDataAppendNULL(pOutputCol, i);
continue;
}
......@@ -831,7 +831,7 @@ static void vectorBitAndHelper(SColumnInfoData* pLeftCol, SColumnInfoData* pRigh
double *output = (double *)pOutputCol->pData;
if (colDataIsNull_f(pRightCol->nullbitmap, 0)) { // Set pLeft->numOfRows NULL value
// TODO set numOfRows NULL value
colDataAppendNNULL(pOutputCol, 0, numOfRows);
} else {
for (; i >= 0 && i < numOfRows; i += step, output += 1) {
*output = getVectorBigintValueFnLeft(pLeftCol->pData, i) & getVectorBigintValueFnRight(pRightCol->pData, 0);
......@@ -888,7 +888,7 @@ static void vectorBitOrHelper(SColumnInfoData* pLeftCol, SColumnInfoData* pRight
int64_t *output = (int64_t *)pOutputCol->pData;
if (colDataIsNull_f(pRightCol->nullbitmap, 0)) { // Set pLeft->numOfRows NULL value
// TODO set numOfRows NULL value
colDataAppendNNULL(pOutputCol, 0, numOfRows);
} else {
int64_t rx = getVectorBigintValueFnRight(pRightCol->pData, 0);
for (; i >= 0 && i < numOfRows; i += step, output += 1) {
......@@ -947,56 +947,51 @@ void vectorCompareImpl(SScalarParam* pLeft, SScalarParam* pRight, SScalarParam *
if (pRight->pHashFilter != NULL) {
for (; i >= 0 && i < pLeft->numOfRows; i += step) {
if (colDataIsNull(pLeft->columnData, pLeft->numOfRows, i, NULL) /*||
colDataIsNull(pRight->columnData, pRight->numOfRows, i, NULL)*/) {
if (colDataIsNull_s(pLeft->columnData, i)) {
continue;
}
char *pLeftData = colDataGetData(pLeft->columnData, i);
bool res = filterDoCompare(fp, optr, pLeftData, pRight->pHashFilter);
colDataAppend(pOut->columnData, i, (char *)&res, false);
colDataAppendInt8(pOut->columnData, i, (int8_t*)&res);
}
return;
}
if (pLeft->numOfRows == pRight->numOfRows) {
for (; i < pRight->numOfRows && i >= 0; i += step) {
if (colDataIsNull(pLeft->columnData, pLeft->numOfRows, i, NULL) ||
colDataIsNull(pRight->columnData, pRight->numOfRows, i, NULL)) {
if (colDataIsNull_s(pLeft->columnData, i) || colDataIsNull_s(pRight->columnData, i)) {
continue; // TODO set null or ignore
}
char *pLeftData = colDataGetData(pLeft->columnData, i);
char *pRightData = colDataGetData(pRight->columnData, i);
bool res = filterDoCompare(fp, optr, pLeftData, pRightData);
colDataAppend(pOut->columnData, i, (char *)&res, false);
colDataAppendInt8(pOut->columnData, i, (int8_t*)&res);
}
} else if (pRight->numOfRows == 1) {
char *pRightData = colDataGetData(pRight->columnData, 0);
ASSERT(pLeft->pHashFilter == NULL);
for (; i >= 0 && i < pLeft->numOfRows; i += step) {
if (colDataIsNull(pLeft->columnData, pLeft->numOfRows, i, NULL) /*||
colDataIsNull(pRight->columnData, pRight->numOfRows, i, NULL)*/) {
if (colDataIsNull_s(pLeft->columnData, i)) {
continue;
}
char *pLeftData = colDataGetData(pLeft->columnData, i);
bool res = filterDoCompare(fp, optr, pLeftData, pRightData);
colDataAppend(pOut->columnData, i, (char *)&res, false);
colDataAppendInt8(pOut->columnData, i, (int8_t*)&res);
}
} else if (pLeft->numOfRows == 1) {
char *pLeftData = colDataGetData(pLeft->columnData, 0);
for (; i >= 0 && i < pRight->numOfRows; i += step) {
if (colDataIsNull(pRight->columnData, pRight->numOfRows, i, NULL) /*||
colDataIsNull(pRight->columnData, pRight->numOfRows, i, NULL)*/) {
if (colDataIsNull_s(pRight->columnData, i)) {
continue;
}
char *pRightData = colDataGetData(pLeft->columnData, i);
bool res = filterDoCompare(fp, optr, pLeftData, pRightData);
colDataAppend(pOut->columnData, i, (char *)&res, false);
colDataAppendInt8(pOut->columnData, i, (int8_t*)&res);
}
}
}
......@@ -1077,23 +1072,16 @@ void vectorNotMatch(SScalarParam* pLeft, SScalarParam* pRight, SScalarParam *pOu
void vectorIsNull(SScalarParam* pLeft, SScalarParam* pRight, SScalarParam *pOut, int32_t _ord) {
for(int32_t i = 0; i < pLeft->numOfRows; ++i) {
int8_t v = 0;
if (colDataIsNull(pLeft->columnData, pLeft->numOfRows, i, NULL)) {
v = 1;
}
colDataAppend(pOut->columnData, i, (char*) &v, false);
int8_t v = colDataIsNull_s(pLeft->columnData, i)? 1:0;
colDataAppendInt8(pOut->columnData, i, &v);
}
pOut->numOfRows = pLeft->numOfRows;
}
void vectorNotNull(SScalarParam* pLeft, SScalarParam* pRight, SScalarParam *pOut, int32_t _ord) {
for(int32_t i = 0; i < pLeft->numOfRows; ++i) {
int8_t v = 1;
if (colDataIsNull(pLeft->columnData, pLeft->numOfRows, i, NULL)) {
v = 0;
}
colDataAppend(pOut->columnData, i, (char*) &v, false);
int8_t v = colDataIsNull_s(pLeft->columnData, i)? 0:1;
colDataAppendInt8(pOut->columnData, i, &v);
}
pOut->numOfRows = pLeft->numOfRows;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册