提交 79002a6e 编写于 作者: G Ganlin Zhao

[TD-14240]<feature>: add math functions

上级 1ad4440b
......@@ -36,7 +36,21 @@ extern struct SScalarFunctionInfo scalarFunc[8];
int32_t evaluateExprNodeTree(tExprNode* pExprs, int32_t numOfRows, SScalarParam* pOutput,
void* param, char* (*getSourceDataBlock)(void*, const char*, int32_t));
int32_t abs_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput);
int32_t log_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput);
int32_t pow_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput);
int32_t sqrt_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput);
int32_t sin_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput);
int32_t cos_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput);
int32_t tan_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput);
int32_t asin_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput);
int32_t acos_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput);
int32_t atan_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput);
int32_t ceil_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput);
int32_t floor_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput);
int32_t round_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput);
#ifdef __cplusplus
}
......
......@@ -4,145 +4,536 @@
static void assignBasicParaInfo(struct SScalarParam* dst, const struct SScalarParam* src) {
dst->type = src->type;
dst->bytes = src->bytes;
dst->num = src->num;
//dst->num = src->num;
}
static void tceil(SScalarParam* pOutput, size_t numOfInput, const SScalarParam *pLeft) {
assignBasicParaInfo(pOutput, pLeft);
assert(numOfInput == 1);
/** Math functions **/
int32_t abs_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) {
assignBasicParaInfo(pOutput, pInput);
if (inputNum != 1 || !IS_NUMERIC_TYPE(pInput->type)) {
return TSDB_CODE_FAILED;
}
switch (pLeft->bytes) {
case TSDB_DATA_TYPE_FLOAT: {
float* p = (float*) pLeft->data;
float* out = (float*) pOutput->data;
for (int32_t i = 0; i < pLeft->num; ++i) {
out[i] = ceilf(p[i]);
char *input = NULL, *output = NULL;
for (int32_t i = 0; i < pOutput->num; ++i) {
if (pInput->num == 1) {
input = pInput->data;
} else {
input = pInput->data + i * pInput->bytes;
}
output = pOutput->data + i * pOutput->bytes;
if (isNull(input, pInput->type)) {
setNull(output, pOutput->type, pOutput->bytes);
continue;
}
switch (pInput->type) {
case TSDB_DATA_TYPE_FLOAT: {
float v;
GET_TYPED_DATA(v, float, pInput->type, input);
float result;
result = (v > 0) ? v : -v;
SET_TYPED_DATA(output, pOutput->type, result);
break;
}
case TSDB_DATA_TYPE_DOUBLE: {
double v;
GET_TYPED_DATA(v, double, pInput->type, input);
double result;
result = (v > 0) ? v : -v;
SET_TYPED_DATA(output, pOutput->type, result);
break;
}
case TSDB_DATA_TYPE_TINYINT: {
int8_t v;
GET_TYPED_DATA(v, int8_t, pInput->type, input);
int8_t result;
result = (v > 0) ? v : -v;
SET_TYPED_DATA(output, pOutput->type, result);
break;
}
case TSDB_DATA_TYPE_SMALLINT: {
int16_t v;
GET_TYPED_DATA(v, int16_t, pInput->type, input);
int16_t result;
result = (v > 0) ? v : -v;
SET_TYPED_DATA(output, pOutput->type, result);
break;
}
case TSDB_DATA_TYPE_INT: {
int32_t v;
GET_TYPED_DATA(v, int32_t, pInput->type, input);
int32_t result;
result = (v > 0) ? v : -v;
SET_TYPED_DATA(output, pOutput->type, result);
break;
}
case TSDB_DATA_TYPE_BIGINT: {
int64_t v;
GET_TYPED_DATA(v, int64_t, pInput->type, input);
int64_t result;
result = (v > 0) ? v : -v;
SET_TYPED_DATA(output, pOutput->type, result);
break;
}
default: {
memcpy(output, input, pInput->bytes);
break;
}
}
}
case TSDB_DATA_TYPE_DOUBLE: {
double* p = (double*) pLeft->data;
double* out = (double*)pOutput->data;
for (int32_t i = 0; i < pLeft->num; ++i) {
out[i] = ceil(p[i]);
return TSDB_CODE_SUCCESS;
}
int32_t log_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) {
if (inputNum != 2 || !IS_NUMERIC_TYPE(pInput[0].type) || !IS_NUMERIC_TYPE(pInput[1].type)) {
return TSDB_CODE_FAILED;
}
pOutput->type = TSDB_DATA_TYPE_DOUBLE;
pOutput->bytes = tDataTypes[TSDB_DATA_TYPE_DOUBLE].bytes;
char **input = NULL, *output = NULL;
bool hasNullInput = false;
input = calloc(inputNum, sizeof(char *));
for (int32_t i = 0; i < pOutput->num; ++i) {
for (int32_t j = 0; j < inputNum; ++j) {
if (pInput[j].num == 1) {
input[j] = pInput[j].data;
} else {
input[j] = pInput[j].data + i * pInput[j].bytes;
}
if (isNull(input[j], pInput[j].type)) {
hasNullInput = true;
break;
}
}
output = pOutput->data + i * pOutput->bytes;
default:
memcpy(pOutput->data, pLeft->data, pLeft->num* pLeft->bytes);
if (hasNullInput) {
setNull(output, pOutput->type, pOutput->bytes);
continue;
}
double base;
GET_TYPED_DATA(base, double, pInput[1].type, input[1]);
double v;
GET_TYPED_DATA(v, double, pInput[0].type, input[0]);
double result = log(v) / log(base);
SET_TYPED_DATA(output, pOutput->type, result);
}
}
static void tfloor(SScalarParam* pOutput, size_t numOfInput, const SScalarParam *pLeft) {
assignBasicParaInfo(pOutput, pLeft);
assert(numOfInput == 1);
free(input);
switch (pLeft->bytes) {
case TSDB_DATA_TYPE_FLOAT: {
float* p = (float*) pLeft->data;
float* out = (float*) pOutput->data;
return TSDB_CODE_SUCCESS;
}
int32_t pow_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) {
if (inputNum != 2 || !IS_NUMERIC_TYPE(pInput[0].type) || !IS_NUMERIC_TYPE(pInput[1].type)) {
return TSDB_CODE_FAILED;
}
for (int32_t i = 0; i < pLeft->num; ++i) {
out[i] = floorf(p[i]);
pOutput->type = TSDB_DATA_TYPE_DOUBLE;
pOutput->bytes = tDataTypes[TSDB_DATA_TYPE_DOUBLE].bytes;
char **input = NULL, *output = NULL;
bool hasNullInput = false;
input = calloc(inputNum, sizeof(char *));
for (int32_t i = 0; i < pOutput->num; ++i) {
for (int32_t j = 0; j < inputNum; ++j) {
if (pInput[j].num == 1) {
input[j] = pInput[j].data;
} else {
input[j] = pInput[j].data + i * pInput[j].bytes;
}
if (isNull(input[j], pInput[j].type)) {
hasNullInput = true;
break;
}
}
output = pOutput->data + i * pOutput->bytes;
case TSDB_DATA_TYPE_DOUBLE: {
double* p = (double*) pLeft->data;
double* out = (double*) pOutput->data;
if (hasNullInput) {
setNull(output, pOutput->type, pOutput->bytes);
continue;
}
for (int32_t i = 0; i < pLeft->num; ++i) {
out[i] = floor(p[i]);
}
double base;
GET_TYPED_DATA(base, double, pInput[1].type, input[1]);
double v;
GET_TYPED_DATA(v, double, pInput[0].type, input[0]);
double result = pow(v, base);
SET_TYPED_DATA(output, pOutput->type, result);
}
free(input);
return TSDB_CODE_SUCCESS;
}
int32_t sqrt_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) {
if (inputNum != 1 || !IS_NUMERIC_TYPE(pInput->type)) {
return TSDB_CODE_FAILED;
}
pOutput->type = TSDB_DATA_TYPE_DOUBLE;
pOutput->bytes = tDataTypes[TSDB_DATA_TYPE_DOUBLE].bytes;
char *input = NULL, *output = NULL;
for (int32_t i = 0; i < pOutput->num; ++i) {
if (pInput->num == 1) {
input = pInput->data;
} else {
input = pInput->data + i * pInput->bytes;
}
output = pOutput->data + i * pOutput->bytes;
if (isNull(input, pInput->type)) {
setNull(output, pOutput->type, pOutput->bytes);
continue;
}
default:
memcpy(pOutput->data, pLeft->data, pLeft->num* pLeft->bytes);
double v;
GET_TYPED_DATA(v, double, pInput->type, input);
double result = sqrt(v);
SET_TYPED_DATA(output, pOutput->type, result);
}
return TSDB_CODE_SUCCESS;
}
static void _tabs(SScalarParam* pOutput, size_t numOfInput, const SScalarParam *pLeft) {
assignBasicParaInfo(pOutput, pLeft);
assert(numOfInput == 1);
int32_t sin_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) {
if (inputNum != 1 || !IS_NUMERIC_TYPE(pInput->type)) {
return TSDB_CODE_FAILED;
}
switch (pLeft->bytes) {
case TSDB_DATA_TYPE_FLOAT: {
float* p = (float*) pLeft->data;
float* out = (float*) pOutput->data;
for (int32_t i = 0; i < pLeft->num; ++i) {
out[i] = (p[i] > 0)? p[i]:-p[i];
}
pOutput->type = TSDB_DATA_TYPE_DOUBLE;
pOutput->bytes = tDataTypes[TSDB_DATA_TYPE_DOUBLE].bytes;
char *input = NULL, *output = NULL;
for (int32_t i = 0; i < pOutput->num; ++i) {
if (pInput->num == 1) {
input = pInput->data;
} else {
input = pInput->data + i * pInput->bytes;
}
output = pOutput->data + i * pOutput->bytes;
case TSDB_DATA_TYPE_DOUBLE: {
double* p = (double*) pLeft->data;
double* out = (double*) pOutput->data;
for (int32_t i = 0; i < pLeft->num; ++i) {
out[i] = (p[i] > 0)? p[i]:-p[i];
}
if (isNull(input, pInput->type)) {
setNull(output, pOutput->type, pOutput->bytes);
continue;
}
case TSDB_DATA_TYPE_TINYINT: {
int8_t* p = (int8_t*) pLeft->data;
int8_t* out = (int8_t*) pOutput->data;
for (int32_t i = 0; i < pLeft->num; ++i) {
out[i] = (p[i] > 0)? p[i]:-p[i];
}
double v;
GET_TYPED_DATA(v, double, pInput->type, input);
double result = sin(v);
SET_TYPED_DATA(output, pOutput->type, result);
}
return TSDB_CODE_SUCCESS;
}
int32_t cos_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) {
if (inputNum != 1 || !IS_NUMERIC_TYPE(pInput->type)) {
return TSDB_CODE_FAILED;
}
pOutput->type = TSDB_DATA_TYPE_DOUBLE;
pOutput->bytes = tDataTypes[TSDB_DATA_TYPE_DOUBLE].bytes;
char *input = NULL, *output = NULL;
for (int32_t i = 0; i < pOutput->num; ++i) {
if (pInput->num == 1) {
input = pInput->data;
} else {
input = pInput->data + i * pInput->bytes;
}
output = pOutput->data + i * pOutput->bytes;
case TSDB_DATA_TYPE_SMALLINT: {
int16_t* p = (int16_t*) pLeft->data;
int16_t* out = (int16_t*) pOutput->data;
for (int32_t i = 0; i < pLeft->num; ++i) {
out[i] = (p[i] > 0)? p[i]:-p[i];
}
if (isNull(input, pInput->type)) {
setNull(output, pOutput->type, pOutput->bytes);
continue;
}
case TSDB_DATA_TYPE_INT: {
int32_t* p = (int32_t*) pLeft->data;
int32_t* out = (int32_t*) pOutput->data;
for (int32_t i = 0; i < pLeft->num; ++i) {
out[i] = (p[i] > 0)? p[i]:-p[i];
}
double v;
GET_TYPED_DATA(v, double, pInput->type, input);
double result = cos(v);
SET_TYPED_DATA(output, pOutput->type, result);
}
return TSDB_CODE_SUCCESS;
}
int32_t tan_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) {
if (inputNum != 1 || !IS_NUMERIC_TYPE(pInput->type)) {
return TSDB_CODE_FAILED;
}
pOutput->type = TSDB_DATA_TYPE_DOUBLE;
pOutput->bytes = tDataTypes[TSDB_DATA_TYPE_DOUBLE].bytes;
char *input = NULL, *output = NULL;
for (int32_t i = 0; i < pOutput->num; ++i) {
if (pInput->num == 1) {
input = pInput->data;
} else {
input = pInput->data + i * pInput->bytes;
}
output = pOutput->data + i * pOutput->bytes;
case TSDB_DATA_TYPE_BIGINT: {
int64_t* p = (int64_t*) pLeft->data;
int64_t* out = (int64_t*) pOutput->data;
for (int32_t i = 0; i < pLeft->num; ++i) {
out[i] = (p[i] > 0)? p[i]:-p[i];
}
if (isNull(input, pInput->type)) {
setNull(output, pOutput->type, pOutput->bytes);
continue;
}
double v;
GET_TYPED_DATA(v, double, pInput->type, input);
double result = tan(v);
SET_TYPED_DATA(output, pOutput->type, result);
}
return TSDB_CODE_SUCCESS;
}
int32_t asin_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) {
if (inputNum != 1 || !IS_NUMERIC_TYPE(pInput->type)) {
return TSDB_CODE_FAILED;
}
pOutput->type = TSDB_DATA_TYPE_DOUBLE;
pOutput->bytes = tDataTypes[TSDB_DATA_TYPE_DOUBLE].bytes;
char *input = NULL, *output = NULL;
for (int32_t i = 0; i < pOutput->num; ++i) {
if (pInput->num == 1) {
input = pInput->data;
} else {
input = pInput->data + i * pInput->bytes;
}
output = pOutput->data + i * pOutput->bytes;
if (isNull(input, pInput->type)) {
setNull(output, pOutput->type, pOutput->bytes);
continue;
}
default:
memcpy(pOutput->data, pLeft->data, pLeft->num* pLeft->bytes);
double v;
GET_TYPED_DATA(v, double, pInput->type, input);
double result = asin(v);
SET_TYPED_DATA(output, pOutput->type, result);
}
return TSDB_CODE_SUCCESS;
}
static void tround(SScalarParam* pOutput, size_t numOfInput, const SScalarParam *pLeft) {
assignBasicParaInfo(pOutput, pLeft);
assert(numOfInput == 1);
int32_t acos_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) {
if (inputNum != 1 || !IS_NUMERIC_TYPE(pInput->type)) {
return TSDB_CODE_FAILED;
}
switch (pLeft->bytes) {
case TSDB_DATA_TYPE_FLOAT: {
float* p = (float*) pLeft->data;
float* out = (float*) pOutput->data;
for (int32_t i = 0; i < pLeft->num; ++i) {
out[i] = roundf(p[i]);
pOutput->type = TSDB_DATA_TYPE_DOUBLE;
pOutput->bytes = tDataTypes[TSDB_DATA_TYPE_DOUBLE].bytes;
char *input = NULL, *output = NULL;
for (int32_t i = 0; i < pOutput->num; ++i) {
if (pInput->num == 1) {
input = pInput->data;
} else {
input = pInput->data + i * pInput->bytes;
}
output = pOutput->data + i * pOutput->bytes;
if (isNull(input, pInput->type)) {
setNull(output, pOutput->type, pOutput->bytes);
continue;
}
double v;
GET_TYPED_DATA(v, double, pInput->type, input);
double result = acos(v);
SET_TYPED_DATA(output, pOutput->type, result);
}
return TSDB_CODE_SUCCESS;
}
int32_t atan_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) {
if (inputNum != 1 || !IS_NUMERIC_TYPE(pInput->type)) {
return TSDB_CODE_FAILED;
}
pOutput->type = TSDB_DATA_TYPE_DOUBLE;
pOutput->bytes = tDataTypes[TSDB_DATA_TYPE_DOUBLE].bytes;
char *input = NULL, *output = NULL;
for (int32_t i = 0; i < pOutput->num; ++i) {
if (pInput->num == 1) {
input = pInput->data;
} else {
input = pInput->data + i * pInput->bytes;
}
output = pOutput->data + i * pOutput->bytes;
if (isNull(input, pInput->type)) {
setNull(output, pOutput->type, pOutput->bytes);
continue;
}
double v;
GET_TYPED_DATA(v, double, pInput->type, input);
double result = atan(v);
SET_TYPED_DATA(output, pOutput->type, result);
}
return TSDB_CODE_SUCCESS;
}
int32_t ceil_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) {
if (inputNum != 1 || !IS_NUMERIC_TYPE(pInput->type)) {
return TSDB_CODE_FAILED;
}
char *input = NULL, *output = NULL;
for (int32_t i = 0; i < pOutput->num; ++i) {
if (pInput->num == 1) {
input = pInput->data;
} else {
input = pInput->data + i * pInput->bytes;
}
output = pOutput->data + i * pOutput->bytes;
if (isNull(input, pInput->type)) {
setNull(output, pOutput->type, pOutput->bytes);
continue;
}
switch (pInput->type) {
case TSDB_DATA_TYPE_FLOAT: {
float v;
GET_TYPED_DATA(v, float, pInput->type, input);
float result = ceilf(v);
SET_TYPED_DATA(output, pOutput->type, result);
break;
}
case TSDB_DATA_TYPE_DOUBLE: {
double v;
GET_TYPED_DATA(v, double, pInput->type, input);
double result = ceil(v);
SET_TYPED_DATA(output, pOutput->type, result);
break;
}
default: {
memcpy(output, input, pInput->bytes);
break;
}
}
}
case TSDB_DATA_TYPE_DOUBLE: {
double* p = (double*) pLeft->data;
double* out = (double*) pOutput->data;
for (int32_t i = 0; i < pLeft->num; ++i) {
out[i] = round(p[i]);
return TSDB_CODE_SUCCESS;
}
int32_t floor_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) {
assignBasicParaInfo(pOutput, pInput);
if (inputNum != 1 || !IS_NUMERIC_TYPE(pInput->type)) {
return TSDB_CODE_FAILED;
}
char *input = NULL, *output = NULL;
for (int32_t i = 0; i < pOutput->num; ++i) {
if (pInput->num == 1) {
input = pInput->data;
} else {
input = pInput->data + i * pInput->bytes;
}
output = pOutput->data + i * pOutput->bytes;
if (isNull(input, pInput->type)) {
setNull(output, pOutput->type, pOutput->bytes);
continue;
}
switch (pInput->type) {
case TSDB_DATA_TYPE_FLOAT: {
float v;
GET_TYPED_DATA(v, float, pInput->type, input);
float result = floorf(v);
SET_TYPED_DATA(output, pOutput->type, result);
break;
}
case TSDB_DATA_TYPE_DOUBLE: {
double v;
GET_TYPED_DATA(v, double, pInput->type, input);
double result = floor(v);
SET_TYPED_DATA(output, pOutput->type, result);
break;
}
default: {
memcpy(output, input, pInput->bytes);
break;
}
}
}
return TSDB_CODE_SUCCESS;
}
int32_t round_function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) {
assignBasicParaInfo(pOutput, pInput);
if (inputNum != 1 || !IS_NUMERIC_TYPE(pInput->type)) {
return TSDB_CODE_FAILED;
}
char *input = NULL, *output = NULL;
for (int32_t i = 0; i < pOutput->num; ++i) {
if (pInput->num == 1) {
input = pInput->data;
} else {
input = pInput->data + i * pInput->bytes;
}
output = pOutput->data + i * pOutput->bytes;
if (isNull(input, pInput->type)) {
setNull(output, pOutput->type, pOutput->bytes);
continue;
}
default:
memcpy(pOutput->data, pLeft->data, pLeft->num* pLeft->bytes);
switch (pInput->type) {
case TSDB_DATA_TYPE_FLOAT: {
float v;
GET_TYPED_DATA(v, float, pInput->type, input);
float result = roundf(v);
SET_TYPED_DATA(output, pOutput->type, result);
break;
}
case TSDB_DATA_TYPE_DOUBLE: {
double v;
GET_TYPED_DATA(v, double, pInput->type, input);
double result = round(v);
SET_TYPED_DATA(output, pOutput->type, result);
break;
}
default: {
memcpy(output, input, pInput->bytes);
break;
}
}
}
return TSDB_CODE_SUCCESS;
}
static void tlength(SScalarParam* pOutput, size_t numOfInput, const SScalarParam *pLeft) {
......@@ -361,16 +752,16 @@ int32_t evaluateExprNodeTree(tExprNode* pExprs, int32_t numOfRows, SScalarFuncPa
#endif
SScalarFunctionInfo scalarFunc[8] = {
{"ceil", FUNCTION_TYPE_SCALAR, FUNCTION_CEIL, tceil},
{"floor", FUNCTION_TYPE_SCALAR, FUNCTION_FLOOR, tfloor},
{"abs", FUNCTION_TYPE_SCALAR, FUNCTION_ABS, _tabs},
{"round", FUNCTION_TYPE_SCALAR, FUNCTION_ROUND, tround},
{"length", FUNCTION_TYPE_SCALAR, FUNCTION_LENGTH, tlength},
{"concat", FUNCTION_TYPE_SCALAR, FUNCTION_CONCAT, tconcat},
{"ltrim", FUNCTION_TYPE_SCALAR, FUNCTION_LTRIM, tltrim},
{"rtrim", FUNCTION_TYPE_SCALAR, FUNCTION_RTRIM, trtrim},
};
//SScalarFunctionInfo scalarFunc[8] = {
// {"ceil", FUNCTION_TYPE_SCALAR, FUNCTION_CEIL, tceil},
// {"floor", FUNCTION_TYPE_SCALAR, FUNCTION_FLOOR, tfloor},
// {"abs", FUNCTION_TYPE_SCALAR, FUNCTION_ABS, _tabs},
// {"round", FUNCTION_TYPE_SCALAR, FUNCTION_ROUND, tround},
// {"length", FUNCTION_TYPE_SCALAR, FUNCTION_LENGTH, tlength},
// {"concat", FUNCTION_TYPE_SCALAR, FUNCTION_CONCAT, tconcat},
// {"ltrim", FUNCTION_TYPE_SCALAR, FUNCTION_LTRIM, tltrim},
// {"rtrim", FUNCTION_TYPE_SCALAR, FUNCTION_RTRIM, trtrim},
//};
void setScalarFunctionSupp(struct SScalarFunctionSupport* sas, SExprInfo *pExprInfo, SSDataBlock* pSDataBlock) {
sas->numOfCols = (int32_t) pSDataBlock->info.numOfCols;
......@@ -411,4 +802,4 @@ void destroyScalarFuncSupport(struct SScalarFunctionSupport* pSupport, int32_t n
}
tfree(pSupport);
}
\ No newline at end of file
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册