Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
taosdata
TDengine
提交
ee5b67a9
T
TDengine
项目概览
taosdata
/
TDengine
接近 2 年 前同步成功
通知
1191
Star
22018
Fork
4786
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
1
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
T
TDengine
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
1
Issue
1
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
提交
ee5b67a9
编写于
5月 01, 2022
作者:
dengyihao
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
enh(index): support index filter
上级
60b05c8a
变更
3
隐藏空白更改
内联
并排
Showing
3 changed file
with
140 addition
and
66 deletion
+140
-66
source/libs/executor/CMakeLists.txt
source/libs/executor/CMakeLists.txt
+2
-2
source/libs/executor/src/indexoperator.c
source/libs/executor/src/indexoperator.c
+120
-47
source/libs/index/src/index.c
source/libs/index/src/index.c
+18
-17
未找到文件。
source/libs/executor/CMakeLists.txt
浏览文件 @
ee5b67a9
...
@@ -8,7 +8,7 @@ add_library(executor STATIC ${EXECUTOR_SRC})
...
@@ -8,7 +8,7 @@ add_library(executor STATIC ${EXECUTOR_SRC})
# )
# )
target_link_libraries
(
executor
target_link_libraries
(
executor
PRIVATE os util common function parser planner qcom vnode scalar nodes
PRIVATE os util common function parser planner qcom vnode scalar nodes
index
)
)
target_include_directories
(
target_include_directories
(
...
@@ -19,4 +19,4 @@ target_include_directories(
...
@@ -19,4 +19,4 @@ target_include_directories(
#if(${BUILD_TEST})
#if(${BUILD_TEST})
ADD_SUBDIRECTORY
(
test
)
ADD_SUBDIRECTORY
(
test
)
#endif(${BUILD_TEST})
#endif(${BUILD_TEST})
\ No newline at end of file
source/libs/executor/src/indexoperator.c
浏览文件 @
ee5b67a9
...
@@ -15,7 +15,9 @@
...
@@ -15,7 +15,9 @@
#include "indexoperator.h"
#include "indexoperator.h"
#include "executorimpl.h"
#include "executorimpl.h"
#include "index.h"
#include "nodes.h"
#include "nodes.h"
#include "tdatablock.h"
typedef
struct
SIFCtx
{
typedef
struct
SIFCtx
{
int32_t
code
;
int32_t
code
;
...
@@ -48,11 +50,19 @@ typedef struct SIFCtx {
...
@@ -48,11 +50,19 @@ typedef struct SIFCtx {
} while (0)
} while (0)
typedef
struct
SIFParam
{
typedef
struct
SIFParam
{
SArray
*
result
;
SHashObj
*
pFilter
;
SHashObj
*
pFilter
;
SArray
*
result
;
char
*
condValue
;
col_id_t
colId
;
int64_t
suid
;
// add later
char
dbName
[
TSDB_DB_NAME_LEN
];
char
colName
[
TSDB_COL_NAME_LEN
];
}
SIFParam
;
}
SIFParam
;
typedef
int32_t
(
*
sif_func_t
)(
S
Node
*
left
,
SNode
*
rigth
,
SIFParam
*
output
);
typedef
int32_t
(
*
sif_func_t
)(
S
IFParam
*
left
,
SIFParam
*
rigth
,
SIFParam
*
output
);
// construct tag filter operator later
// construct tag filter operator later
static
void
destroyTagFilterOperatorInfo
(
void
*
param
)
{
static
void
destroyTagFilterOperatorInfo
(
void
*
param
)
{
STagFilterOperatorInfo
*
pInfo
=
(
STagFilterOperatorInfo
*
)
param
;
STagFilterOperatorInfo
*
pInfo
=
(
STagFilterOperatorInfo
*
)
param
;
...
@@ -60,7 +70,10 @@ static void destroyTagFilterOperatorInfo(void *param) {
...
@@ -60,7 +70,10 @@ static void destroyTagFilterOperatorInfo(void *param) {
static
void
sifFreeParam
(
SIFParam
*
param
)
{
static
void
sifFreeParam
(
SIFParam
*
param
)
{
if
(
param
==
NULL
)
return
;
if
(
param
==
NULL
)
return
;
taosArrayDestroy
(
param
->
result
);
taosArrayDestroy
(
param
->
result
);
taosMemoryFree
(
param
->
condValue
);
taosHashCleanup
(
param
->
pFilter
);
}
}
static
int32_t
sifGetOperParamNum
(
EOperatorType
ty
)
{
static
int32_t
sifGetOperParamNum
(
EOperatorType
ty
)
{
...
@@ -71,15 +84,70 @@ static int32_t sifGetOperParamNum(EOperatorType ty) {
...
@@ -71,15 +84,70 @@ static int32_t sifGetOperParamNum(EOperatorType ty) {
}
}
return
2
;
return
2
;
}
}
static
int32_t
sifValidateColumn
(
SColumnNode
*
cn
)
{
// add more check
if
(
cn
==
NULL
)
{
return
TSDB_CODE_QRY_INVALID_INPUT
;
}
if
(
cn
->
colType
!=
COLUMN_TYPE_TAG
)
{
return
TSDB_CODE_QRY_INVALID_INPUT
;
}
return
TSDB_CODE_SUCCESS
;
}
static
int32_t
sifGetValueFromNode
(
SNode
*
node
,
char
**
value
)
{
// covert data From snode;
SValueNode
*
vn
=
(
SValueNode
*
)
node
;
char
*
pData
=
nodesGetValueFromNode
(
vn
);
SDataType
*
pType
=
&
vn
->
node
.
resType
;
int32_t
type
=
pType
->
type
;
int32_t
valLen
=
0
;
if
(
IS_VAR_DATA_TYPE
(
type
))
{
int32_t
dataLen
=
varDataTLen
(
pData
);
if
(
type
==
TSDB_DATA_TYPE_JSON
)
{
if
(
*
pData
==
TSDB_DATA_TYPE_NULL
)
{
dataLen
=
0
;
}
else
if
(
*
pData
==
TSDB_DATA_TYPE_NCHAR
)
{
dataLen
=
varDataTLen
(
pData
+
CHAR_BYTES
);
}
else
if
(
*
pData
==
TSDB_DATA_TYPE_BIGINT
||
*
pData
==
TSDB_DATA_TYPE_DOUBLE
)
{
dataLen
=
LONG_BYTES
;
}
else
if
(
*
pData
==
TSDB_DATA_TYPE_BOOL
)
{
dataLen
=
CHAR_BYTES
;
}
dataLen
+=
CHAR_BYTES
;
}
valLen
=
dataLen
;
}
else
{
valLen
=
pType
->
bytes
;
}
char
*
tv
=
taosMemoryCalloc
(
1
,
valLen
+
1
);
if
(
tv
==
NULL
)
{
return
TSDB_CODE_QRY_OUT_OF_MEMORY
;
}
memcpy
(
tv
,
pData
,
valLen
);
*
value
=
tv
;
return
TSDB_CODE_SUCCESS
;
}
static
int32_t
sifInitParam
(
SNode
*
node
,
SIFParam
*
param
,
SIFCtx
*
ctx
)
{
static
int32_t
sifInitParam
(
SNode
*
node
,
SIFParam
*
param
,
SIFCtx
*
ctx
)
{
switch
(
nodeType
(
node
))
{
switch
(
nodeType
(
node
))
{
case
QUERY_NODE_VALUE
:
{
case
QUERY_NODE_VALUE
:
{
SValueNode
*
vn
=
(
SValueNode
*
)
node
;
SValueNode
*
vn
=
(
SValueNode
*
)
node
;
SIF_ERR_RET
(
sifGetValueFromNode
(
node
,
&
param
->
condValue
));
param
->
colId
=
-
1
;
break
;
break
;
}
}
case
QUERY_NODE_COLUMN
:
{
case
QUERY_NODE_COLUMN
:
{
SColumnNode
*
cn
=
(
SColumnNode
*
)
node
;
SColumnNode
*
cn
=
(
SColumnNode
*
)
node
;
/*only support tag column*/
SIF_ERR_RET
(
sifValidateColumn
(
cn
));
param
->
colId
=
cn
->
colId
;
memcpy
(
param
->
dbName
,
cn
->
dbName
,
sizeof
(
cn
->
dbName
));
memcpy
(
param
->
colName
,
cn
->
colName
,
sizeof
(
cn
->
colName
));
break
;
break
;
}
}
...
@@ -89,7 +157,7 @@ static int32_t sifInitParam(SNode *node, SIFParam *param, SIFCtx *ctx) {
...
@@ -89,7 +157,7 @@ static int32_t sifInitParam(SNode *node, SIFParam *param, SIFCtx *ctx) {
qError
(
"invalid length for node:%p, length: %d"
,
node
,
LIST_LENGTH
(
nl
->
pNodeList
));
qError
(
"invalid length for node:%p, length: %d"
,
node
,
LIST_LENGTH
(
nl
->
pNodeList
));
SIF_ERR_RET
(
TSDB_CODE_QRY_INVALID_INPUT
);
SIF_ERR_RET
(
TSDB_CODE_QRY_INVALID_INPUT
);
}
}
SIF_ERR_RET
(
scalarGenerateSetFromList
((
void
**
)
&
param
->
pFilter
,
node
,
nl
->
dataType
.
type
));
if
(
taosHashPut
(
ctx
->
pRes
,
&
node
,
POINTER_BYTES
,
param
,
sizeof
(
*
param
)))
{
if
(
taosHashPut
(
ctx
->
pRes
,
&
node
,
POINTER_BYTES
,
param
,
sizeof
(
*
param
)))
{
taosHashCleanup
(
param
->
pFilter
);
taosHashCleanup
(
param
->
pFilter
);
qError
(
"taosHashPut nodeList failed, size:%d"
,
(
int32_t
)
sizeof
(
*
param
));
qError
(
"taosHashPut nodeList failed, size:%d"
,
(
int32_t
)
sizeof
(
*
param
));
...
@@ -163,58 +231,63 @@ static int32_t sifExecFunction(SFunctionNode *node, SIFCtx *ctx, SIFParam *outpu
...
@@ -163,58 +231,63 @@ static int32_t sifExecFunction(SFunctionNode *node, SIFCtx *ctx, SIFParam *outpu
qError
(
"index-filter not support buildin function"
);
qError
(
"index-filter not support buildin function"
);
return
TSDB_CODE_QRY_INVALID_INPUT
;
return
TSDB_CODE_QRY_INVALID_INPUT
;
}
}
static
int32_t
sifIndex
(
SIFParam
*
left
,
SIFParam
*
right
,
int8_t
operType
,
SIFParam
*
output
)
{
static
int32_t
sifLessThanFunc
(
SNode
*
left
,
SNode
*
rigth
,
SIFParam
*
output
)
{
SIndexMultiTermQuery
*
mq
=
indexMultiTermQueryCreate
(
MUST
);
// impl later
return
TSDB_CODE_SUCCESS
;
return
TSDB_CODE_SUCCESS
;
}
}
static
int32_t
sifLessEqualFunc
(
SNode
*
left
,
SNode
*
rigth
,
SIFParam
*
output
)
{
// impl later
static
int32_t
sifLessThanFunc
(
SIFParam
*
left
,
SIFParam
*
right
,
SIFParam
*
output
)
{
return
TSDB_CODE_SUCCESS
;
int
id
=
OP_TYPE_LOWER_THAN
;
return
sifIndex
(
left
,
right
,
id
,
output
);
}
}
static
int32_t
sif
GreaterThanFunc
(
SNode
*
left
,
SNode
*
rigth
,
SIFParam
*
output
)
{
static
int32_t
sif
LessEqualFunc
(
SIFParam
*
left
,
SIFParam
*
right
,
SIFParam
*
output
)
{
// impl later
int
id
=
OP_TYPE_LOWER_EQUAL
;
return
TSDB_CODE_SUCCESS
;
return
sifIndex
(
left
,
right
,
id
,
output
)
;
}
}
static
int32_t
sifGreaterEqualFunc
(
SNode
*
left
,
SNode
*
rigth
,
SIFParam
*
output
)
{
// impl later
static
int32_t
sifGreaterThanFunc
(
SIFParam
*
left
,
SIFParam
*
right
,
SIFParam
*
output
)
{
return
TSDB_CODE_SUCCESS
;
int
id
=
OP_TYPE_GREATER_THAN
;
return
sifIndex
(
left
,
right
,
id
,
output
);
}
static
int32_t
sifGreaterEqualFunc
(
SIFParam
*
left
,
SIFParam
*
right
,
SIFParam
*
output
)
{
int
id
=
OP_TYPE_GREATER_EQUAL
;
return
sifIndex
(
left
,
right
,
id
,
output
);
}
}
static
int32_t
sifEqualFunc
(
S
Node
*
left
,
SNode
*
rigth
,
SIFParam
*
output
)
{
static
int32_t
sifEqualFunc
(
S
IFParam
*
left
,
SIFParam
*
right
,
SIFParam
*
output
)
{
// impl later
int
id
=
OP_TYPE_EQUAL
;
return
TSDB_CODE_SUCCESS
;
return
sifIndex
(
left
,
right
,
id
,
output
)
;
}
}
static
int32_t
sifNotEqualFunc
(
S
Node
*
left
,
SNode
*
rigth
,
SIFParam
*
output
)
{
static
int32_t
sifNotEqualFunc
(
S
IFParam
*
left
,
SIFParam
*
right
,
SIFParam
*
output
)
{
// impl later
int
id
=
OP_TYPE_NOT_EQUAL
;
return
TSDB_CODE_SUCCESS
;
return
sifIndex
(
left
,
right
,
id
,
output
)
;
}
}
static
int32_t
sifInFunc
(
S
Node
*
left
,
SNode
*
rigth
,
SIFParam
*
output
)
{
static
int32_t
sifInFunc
(
S
IFParam
*
left
,
SIFParam
*
right
,
SIFParam
*
output
)
{
// impl later
int
id
=
OP_TYPE_IN
;
return
TSDB_CODE_SUCCESS
;
return
sifIndex
(
left
,
right
,
id
,
output
)
;
}
}
static
int32_t
sifNotInFunc
(
S
Node
*
left
,
SNode
*
right
,
SIFParam
*
output
)
{
static
int32_t
sifNotInFunc
(
S
IFParam
*
left
,
SIFParam
*
right
,
SIFParam
*
output
)
{
// impl later
int
id
=
OP_TYPE_NOT_IN
;
return
TSDB_CODE_SUCCESS
;
return
sifIndex
(
left
,
right
,
id
,
output
)
;
}
}
static
int32_t
sifLikeFunc
(
S
Node
*
left
,
SNode
*
right
,
SIFParam
*
output
)
{
static
int32_t
sifLikeFunc
(
S
IFParam
*
left
,
SIFParam
*
right
,
SIFParam
*
output
)
{
// impl later
int
id
=
OP_TYPE_LIKE
;
return
TSDB_CODE_SUCCESS
;
return
sifIndex
(
left
,
right
,
id
,
output
)
;
}
}
static
int32_t
sifNotLikeFunc
(
S
Node
*
left
,
SNode
*
right
,
SIFParam
*
output
)
{
static
int32_t
sifNotLikeFunc
(
S
IFParam
*
left
,
SIFParam
*
right
,
SIFParam
*
output
)
{
// impl later
int
id
=
OP_TYPE_NOT_LIKE
;
return
TSDB_CODE_SUCCESS
;
return
sifIndex
(
left
,
right
,
id
,
output
)
;
}
}
static
int32_t
sifMatchFunc
(
S
Node
*
left
,
SNode
*
rigth
,
SIFParam
*
output
)
{
static
int32_t
sifMatchFunc
(
S
IFParam
*
left
,
SIFParam
*
right
,
SIFParam
*
output
)
{
// impl later
int
id
=
OP_TYPE_MATCH
;
return
TSDB_CODE_SUCCESS
;
return
sifIndex
(
left
,
right
,
id
,
output
)
;
}
}
static
int32_t
sifNotMatchFunc
(
S
Node
*
left
,
SNode
*
rigth
,
SIFParam
*
output
)
{
static
int32_t
sifNotMatchFunc
(
S
IFParam
*
left
,
SIFParam
*
right
,
SIFParam
*
output
)
{
// impl later
int
id
=
OP_TYPE_NMATCH
;
return
TSDB_CODE_SUCCESS
;
return
sifIndex
(
left
,
right
,
id
,
output
)
;
}
}
static
int32_t
sifDefaultFunc
(
S
Node
*
left
,
SNode
*
rigth
,
SIFParam
*
output
)
{
static
int32_t
sifDefaultFunc
(
S
IFParam
*
left
,
SIFParam
*
right
,
SIFParam
*
output
)
{
// add more except
// add more except
return
TSDB_CODE_QRY_INVALID_INPUT
;
return
TSDB_CODE_QRY_INVALID_INPUT
;
}
}
...
@@ -252,17 +325,18 @@ static sif_func_t sifGetOperFn(int32_t funcId) {
...
@@ -252,17 +325,18 @@ static sif_func_t sifGetOperFn(int32_t funcId) {
return
sifDefaultFunc
;
return
sifDefaultFunc
;
}
}
static
int32_t
sifExecOper
(
SOperatorNode
*
node
,
SIFCtx
*
ctx
,
SIFParam
*
output
)
{
static
int32_t
sifExecOper
(
SOperatorNode
*
node
,
SIFCtx
*
ctx
,
SIFParam
*
output
)
{
int32_t
code
=
0
;
int32_t
code
=
0
;
SIFParam
*
params
=
NULL
;
SIF_ERR_RET
(
sifInitOperParams
(
&
params
,
node
,
ctx
));
int32_t
nParam
=
sifGetOperParamNum
(
node
->
opType
);
int32_t
nParam
=
sifGetOperParamNum
(
node
->
opType
);
if
(
nParam
<=
1
)
{
if
(
nParam
<=
1
)
{
SIF_ERR_JRET
(
TSDB_CODE_QRY_INVALID_INPUT
);
SIF_ERR_JRET
(
TSDB_CODE_QRY_INVALID_INPUT
);
}
}
SIFParam
*
params
=
NULL
;
SIF_ERR_RET
(
sifInitOperParams
(
&
params
,
node
,
ctx
));
sif_func_t
operFn
=
sifGetOperFn
(
node
->
opType
);
sif_func_t
operFn
=
sifGetOperFn
(
node
->
opType
);
return
operFn
(
node
->
pLeft
,
node
->
pRight
,
output
);
return
operFn
(
&
params
[
0
],
nParam
>
1
?
&
params
[
1
]
:
NULL
,
output
);
_return:
_return:
taosMemoryFree
(
params
);
taosMemoryFree
(
params
);
SIF_RET
(
code
);
SIF_RET
(
code
);
...
@@ -335,7 +409,6 @@ static EDealRes sifWalkOper(SNode *pNode, void *context) {
...
@@ -335,7 +409,6 @@ static EDealRes sifWalkOper(SNode *pNode, void *context) {
if
(
ctx
->
code
)
{
if
(
ctx
->
code
)
{
return
DEAL_RES_ERROR
;
return
DEAL_RES_ERROR
;
}
}
if
(
taosHashPut
(
ctx
->
pRes
,
&
pNode
,
POINTER_BYTES
,
&
output
,
sizeof
(
output
)))
{
if
(
taosHashPut
(
ctx
->
pRes
,
&
pNode
,
POINTER_BYTES
,
&
output
,
sizeof
(
output
)))
{
ctx
->
code
=
TSDB_CODE_QRY_OUT_OF_MEMORY
;
ctx
->
code
=
TSDB_CODE_QRY_OUT_OF_MEMORY
;
return
DEAL_RES_ERROR
;
return
DEAL_RES_ERROR
;
...
...
source/libs/index/src/index.c
浏览文件 @
ee5b67a9
...
@@ -258,13 +258,13 @@ void indexOptsDestroy(SIndexOpts* opts) {
...
@@ -258,13 +258,13 @@ void indexOptsDestroy(SIndexOpts* opts) {
*
*
*/
*/
SIndexMultiTermQuery
*
indexMultiTermQueryCreate
(
EIndexOperatorType
opera
)
{
SIndexMultiTermQuery
*
indexMultiTermQueryCreate
(
EIndexOperatorType
opera
)
{
SIndexMultiTermQuery
*
p
=
(
SIndexMultiTermQuery
*
)
taosMemoryMalloc
(
sizeof
(
SIndexMultiTermQuery
));
SIndexMultiTermQuery
*
mtq
=
(
SIndexMultiTermQuery
*
)
taosMemoryMalloc
(
sizeof
(
SIndexMultiTermQuery
));
if
(
p
==
NULL
)
{
if
(
mtq
==
NULL
)
{
return
NULL
;
return
NULL
;
}
}
p
->
opera
=
opera
;
mtq
->
opera
=
opera
;
p
->
query
=
taosArrayInit
(
4
,
sizeof
(
SIndexTermQuery
));
mtq
->
query
=
taosArrayInit
(
4
,
sizeof
(
SIndexTermQuery
));
return
p
;
return
mtq
;
}
}
void
indexMultiTermQueryDestroy
(
SIndexMultiTermQuery
*
pQuery
)
{
void
indexMultiTermQueryDestroy
(
SIndexMultiTermQuery
*
pQuery
)
{
for
(
int
i
=
0
;
i
<
taosArrayGetSize
(
pQuery
->
query
);
i
++
)
{
for
(
int
i
=
0
;
i
<
taosArrayGetSize
(
pQuery
->
query
);
i
++
)
{
...
@@ -282,23 +282,24 @@ int indexMultiTermQueryAdd(SIndexMultiTermQuery* pQuery, SIndexTerm* term, EInde
...
@@ -282,23 +282,24 @@ int indexMultiTermQueryAdd(SIndexMultiTermQuery* pQuery, SIndexTerm* term, EInde
SIndexTerm
*
indexTermCreate
(
int64_t
suid
,
SIndexOperOnColumn
oper
,
uint8_t
colType
,
const
char
*
colName
,
SIndexTerm
*
indexTermCreate
(
int64_t
suid
,
SIndexOperOnColumn
oper
,
uint8_t
colType
,
const
char
*
colName
,
int32_t
nColName
,
const
char
*
colVal
,
int32_t
nColVal
)
{
int32_t
nColName
,
const
char
*
colVal
,
int32_t
nColVal
)
{
SIndexTerm
*
t
=
(
SIndexTerm
*
)
taosMemoryCalloc
(
1
,
(
sizeof
(
SIndexTerm
)));
SIndexTerm
*
t
m
=
(
SIndexTerm
*
)
taosMemoryCalloc
(
1
,
(
sizeof
(
SIndexTerm
)));
if
(
t
==
NULL
)
{
if
(
t
m
==
NULL
)
{
return
NULL
;
return
NULL
;
}
}
t
->
suid
=
suid
;
t
m
->
suid
=
suid
;
t
->
operType
=
oper
;
t
m
->
operType
=
oper
;
t
->
colType
=
colType
;
t
m
->
colType
=
colType
;
t
->
colName
=
(
char
*
)
taosMemoryCalloc
(
1
,
nColName
+
1
);
t
m
->
colName
=
(
char
*
)
taosMemoryCalloc
(
1
,
nColName
+
1
);
memcpy
(
t
->
colName
,
colName
,
nColName
);
memcpy
(
t
m
->
colName
,
colName
,
nColName
);
t
->
nColName
=
nColName
;
t
m
->
nColName
=
nColName
;
t
->
colVal
=
(
char
*
)
taosMemoryCalloc
(
1
,
nColVal
+
1
);
tm
->
colVal
=
(
char
*
)
taosMemoryCalloc
(
1
,
nColVal
+
1
);
memcpy
(
t
->
colVal
,
colVal
,
nColVal
);
memcpy
(
tm
->
colVal
,
colVal
,
nColVal
);
t
->
nColVal
=
nColVal
;
tm
->
nColVal
=
nColVal
;
return
t
;
return
tm
;
}
}
void
indexTermDestroy
(
SIndexTerm
*
p
)
{
void
indexTermDestroy
(
SIndexTerm
*
p
)
{
taosMemoryFree
(
p
->
colName
);
taosMemoryFree
(
p
->
colName
);
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录