Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
taosdata
TDengine
提交
95fc626c
T
TDengine
项目概览
taosdata
/
TDengine
大约 1 年 前同步成功
通知
1184
Star
22015
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看板
体验新版 GitCode,发现更多精彩内容 >>
提交
95fc626c
编写于
9月 15, 2021
作者:
wmmhello
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
TD-6129<feature> add json tag support
上级
da48c584
变更
11
隐藏空白更改
内联
并排
Showing
11 changed file
with
133 addition
and
115 deletion
+133
-115
src/client/inc/tscUtil.h
src/client/inc/tscUtil.h
+2
-0
src/client/src/tscParseInsert.c
src/client/src/tscParseInsert.c
+5
-53
src/client/src/tscPrepare.c
src/client/src/tscPrepare.c
+2
-0
src/client/src/tscSQLParser.c
src/client/src/tscSQLParser.c
+5
-53
src/client/src/tscUtil.c
src/client/src/tscUtil.c
+62
-0
src/common/src/tname.c
src/common/src/tname.c
+1
-1
src/common/src/ttypes.c
src/common/src/ttypes.c
+1
-1
src/tsdb/inc/tsdbMeta.h
src/tsdb/inc/tsdbMeta.h
+1
-0
src/tsdb/src/tsdbMeta.c
src/tsdb/src/tsdbMeta.c
+45
-6
src/util/inc/hash.h
src/util/inc/hash.h
+2
-0
src/util/src/hash.c
src/util/src/hash.c
+7
-1
未找到文件。
src/client/inc/tscUtil.h
浏览文件 @
95fc626c
...
...
@@ -368,6 +368,8 @@ void tscRemoveCachedTableMeta(STableMetaInfo* pTableMetaInfo, uint64_t id);
char
*
cloneCurrentDBName
(
SSqlObj
*
pSql
);
int
parseJsontoTagData
(
char
*
json
,
SKVRowBuilder
*
kvRowBuilder
,
char
*
errMsg
,
int16_t
startColId
);
#ifdef __cplusplus
}
#endif
...
...
src/client/src/tscParseInsert.c
浏览文件 @
95fc626c
...
...
@@ -32,7 +32,6 @@
#include "ttoken.h"
#include "tdataformat.h"
#include "cJSON.h"
enum
{
TSDB_USE_SERVER_TS
=
0
,
...
...
@@ -1083,61 +1082,14 @@ static int32_t tscCheckIfCreateTable(char **sqlstr, SSqlObj *pSql, char** boundC
// encode json tag string
if
(
spd
.
numOfBound
==
1
&&
pTagSchema
[
spd
.
boundedColumns
[
0
]].
type
==
TSDB_DATA_TYPE_JSON
){
const
char
*
msg1
=
"json parse error"
;
char
tmp
=
sToken
.
z
[
sToken
.
n
];
sToken
.
z
[
sToken
.
n
]
=
0
;
cJSON
*
root
=
cJSON_Parse
(
sToken
.
z
);
if
(
root
==
NULL
){
tscError
(
"json parse error"
);
return
false
;
}
int
size
=
cJSON_GetArraySize
(
root
);
if
(
!
cJSON_IsObject
(
root
)
||
size
==
0
){
tscError
(
"json error invalide value"
);
}
int
jsonIndex
=
0
;
for
(
int
i
=
0
;
i
<
size
;
i
++
)
{
cJSON
*
item
=
cJSON_GetArrayItem
(
root
,
i
);
if
(
!
item
)
{
tscError
(
"json inner error:%d"
,
i
);
continue
;
}
char
tagVal
[
TSDB_MAX_TAGS_LEN
];
int32_t
output
=
0
;
if
(
!
taosMbsToUcs4
(
item
->
string
,
strlen
(
item
->
string
),
varDataVal
(
tagVal
),
TSDB_MAX_TAGS_LEN
-
VARSTR_HEADER_SIZE
,
&
output
))
{
tscError
(
"json string error:%s|%s"
,
strerror
(
errno
),
item
->
string
);
tdDestroyKVRowBuilder
(
&
kvRowBuilder
);
tscDestroyBoundColumnInfo
(
&
spd
);
return
tscSQLSyntaxErrMsg
(
pInsertParam
->
msg
,
"serizelize json error"
,
NULL
);
}
varDataSetLen
(
tagVal
,
output
);
tdAddColToKVRow
(
&
kvRowBuilder
,
jsonIndex
++
,
TSDB_DATA_TYPE_NCHAR
,
tagVal
);
if
(
item
->
type
==
cJSON_String
){
output
=
0
;
if
(
!
taosMbsToUcs4
(
item
->
valuestring
,
strlen
(
item
->
valuestring
),
varDataVal
(
tagVal
),
TSDB_MAX_TAGS_LEN
-
VARSTR_HEADER_SIZE
,
&
output
))
{
tscError
(
"json string error:%s|%s"
,
strerror
(
errno
),
item
->
string
);
tdDestroyKVRowBuilder
(
&
kvRowBuilder
);
tscDestroyBoundColumnInfo
(
&
spd
);
return
tscSQLSyntaxErrMsg
(
pInsertParam
->
msg
,
"serizelize json error"
,
NULL
);
}
varDataSetLen
(
tagVal
,
output
);
tdAddColToKVRow
(
&
kvRowBuilder
,
jsonIndex
++
,
TSDB_DATA_TYPE_NCHAR
,
tagVal
);
}
else
if
(
item
->
type
==
cJSON_Number
){
*
((
double
*
)
tagVal
)
=
item
->
valuedouble
;
tdAddColToKVRow
(
&
kvRowBuilder
,
jsonIndex
++
,
TSDB_DATA_TYPE_BIGINT
,
tagVal
);
}
else
{
tdDestroyKVRowBuilder
(
&
kvRowBuilder
);
tscDestroyBoundColumnInfo
(
&
spd
);
return
tscSQLSyntaxErrMsg
(
pInsertParam
->
msg
,
"invalidate json value"
,
NULL
);
}
code
=
parseJsontoTagData
(
sToken
.
z
,
&
kvRowBuilder
,
pInsertParam
->
msg
,
pTagSchema
[
spd
.
boundedColumns
[
0
]].
colId
);
if
(
code
!=
TSDB_CODE_SUCCESS
)
{
tdDestroyKVRowBuilder
(
&
kvRowBuilder
);
tscDestroyBoundColumnInfo
(
&
spd
);
return
code
;
}
cJSON_Delete
(
root
);
sToken
.
z
[
sToken
.
n
]
=
tmp
;
}
...
...
src/client/src/tscPrepare.c
浏览文件 @
95fc626c
...
...
@@ -905,6 +905,8 @@ static int doBindBatchParam(STableDataBlocks* pBlock, SParamInfo* param, TAOS_MU
}
varDataSetLen
(
data
+
param
->
offset
,
output
);
}
else
if
(
param
->
type
==
TSDB_DATA_TYPE_JSON
)
{
// todo json
}
}
...
...
src/client/src/tscSQLParser.c
浏览文件 @
95fc626c
...
...
@@ -1438,7 +1438,7 @@ static bool validateTableColumnInfo(SArray* pFieldList, SSqlCmd* pCmd) {
int32_t
nLen
=
0
;
for
(
int32_t
i
=
0
;
i
<
numOfCols
;
++
i
)
{
pField
=
taosArrayGet
(
pFieldList
,
i
);
if
(
!
isValidDataType
(
pField
->
type
))
{
if
(
!
isValidDataType
(
pField
->
type
)
||
pField
->
type
==
TSDB_DATA_TYPE_JSON
)
{
invalidOperationMsg
(
tscGetErrorMsgPayload
(
pCmd
),
msg4
);
return
false
;
}
...
...
@@ -1531,7 +1531,7 @@ static bool validateTagParams(SArray* pTagsList, SArray* pFieldList, SSqlCmd* pC
for
(
int32_t
i
=
0
;
i
<
numOfTags
;
++
i
)
{
TAOS_FIELD
*
p
=
taosArrayGet
(
pTagsList
,
i
);
if
(
!
isValidDataType
(
p
->
type
)
&&
p
->
type
!=
TSDB_DATA_TYPE_JSON
)
{
if
(
!
isValidDataType
(
p
->
type
))
{
invalidOperationMsg
(
tscGetErrorMsgPayload
(
pCmd
),
msg5
);
return
false
;
}
...
...
@@ -7740,8 +7740,6 @@ int32_t doCheckForCreateFromStable(SSqlObj* pSql, SSqlInfo* pInfo) {
const
char
*
msg3
=
"tag value too long"
;
const
char
*
msg4
=
"illegal value or data overflow"
;
const
char
*
msg5
=
"tags number not matched"
;
const
char
*
msg6
=
"tags json invalidate"
;
const
char
*
msg7
=
"serizelize json error"
;
SSqlCmd
*
pCmd
=
&
pSql
->
cmd
;
...
...
@@ -7939,57 +7937,11 @@ int32_t doCheckForCreateFromStable(SSqlObj* pSql, SSqlInfo* pInfo) {
return
invalidOperationMsg
(
tscGetErrorMsgPayload
(
pCmd
),
msg5
);
}
tVariantListItem
*
pItem
=
taosArrayGet
(
pValList
,
0
);
cJSON
*
root
=
cJSON_Parse
(
pItem
->
pVar
.
pz
);
if
(
root
==
NULL
){
tscError
(
"json parse error"
);
tdDestroyKVRowBuilder
(
&
kvRowBuilder
);
return
invalidOperationMsg
(
tscGetErrorMsgPayload
(
pCmd
),
msg6
);
}
int
size
=
cJSON_GetArraySize
(
root
);
if
(
!
cJSON_IsObject
(
root
)
||
size
==
0
){
tscError
(
"json error invalide value"
);
ret
=
parseJsontoTagData
(
pItem
->
pVar
.
pz
,
&
kvRowBuilder
,
tscGetErrorMsgPayload
(
pCmd
),
pTagSchema
[
0
].
colId
);
if
(
ret
!=
TSDB_CODE_SUCCESS
)
{
tdDestroyKVRowBuilder
(
&
kvRowBuilder
);
return
invalidOperationMsg
(
tscGetErrorMsgPayload
(
pCmd
),
msg6
);
}
int
jsonIndex
=
0
;
for
(
int
i
=
0
;
i
<
size
;
i
++
)
{
cJSON
*
item
=
cJSON_GetArrayItem
(
root
,
i
);
if
(
!
item
)
{
tscError
(
"json inner error:%d"
,
i
);
continue
;
}
char
tagVal
[
TSDB_MAX_TAGS_LEN
];
int32_t
output
=
0
;
if
(
!
taosMbsToUcs4
(
item
->
string
,
strlen
(
item
->
string
),
varDataVal
(
tagVal
),
TSDB_MAX_TAGS_LEN
-
VARSTR_HEADER_SIZE
,
&
output
))
{
tscError
(
"json string error:%s|%s"
,
strerror
(
errno
),
item
->
string
);
tdDestroyKVRowBuilder
(
&
kvRowBuilder
);
return
invalidOperationMsg
(
tscGetErrorMsgPayload
(
pCmd
),
msg7
);
}
varDataSetLen
(
tagVal
,
output
);
tdAddColToKVRow
(
&
kvRowBuilder
,
jsonIndex
++
,
TSDB_DATA_TYPE_NCHAR
,
tagVal
);
if
(
item
->
type
==
cJSON_String
){
output
=
0
;
if
(
!
taosMbsToUcs4
(
item
->
valuestring
,
strlen
(
item
->
valuestring
),
varDataVal
(
tagVal
),
TSDB_MAX_TAGS_LEN
-
VARSTR_HEADER_SIZE
,
&
output
))
{
tscError
(
"json string error:%s|%s"
,
strerror
(
errno
),
item
->
string
);
tdDestroyKVRowBuilder
(
&
kvRowBuilder
);
return
invalidOperationMsg
(
tscGetErrorMsgPayload
(
pCmd
),
msg7
);
}
varDataSetLen
(
tagVal
,
output
);
tdAddColToKVRow
(
&
kvRowBuilder
,
jsonIndex
++
,
TSDB_DATA_TYPE_NCHAR
,
tagVal
);
}
else
if
(
item
->
type
==
cJSON_Number
){
*
((
double
*
)
tagVal
)
=
item
->
valuedouble
;
tdAddColToKVRow
(
&
kvRowBuilder
,
jsonIndex
++
,
TSDB_DATA_TYPE_BIGINT
,
tagVal
);
}
else
{
tdDestroyKVRowBuilder
(
&
kvRowBuilder
);
return
invalidOperationMsg
(
tscGetErrorMsgPayload
(
pCmd
),
msg6
);}
return
ret
;
}
cJSON_Delete
(
root
);
}
SKVRow
row
=
tdGetKVRowFromBuilder
(
&
kvRowBuilder
);
...
...
src/client/src/tscUtil.c
浏览文件 @
95fc626c
...
...
@@ -30,6 +30,7 @@
#include "ttimer.h"
#include "ttokendef.h"
#include "httpInt.h"
#include "cJSON.h"
static
void
freeQueryInfoImpl
(
SQueryInfo
*
pQueryInfo
);
...
...
@@ -5150,3 +5151,64 @@ char* cloneCurrentDBName(SSqlObj* pSql) {
return
p
;
}
int
parseJsontoTagData
(
char
*
json
,
SKVRowBuilder
*
kvRowBuilder
,
char
*
errMsg
,
int16_t
startColId
){
cJSON
*
root
=
cJSON_Parse
(
json
);
if
(
root
==
NULL
){
tscError
(
"json parse error"
);
return
tscSQLSyntaxErrMsg
(
errMsg
,
"json parse error"
,
NULL
);
}
int
retCode
=
0
;
int
size
=
cJSON_GetArraySize
(
root
);
if
(
!
cJSON_IsObject
(
root
)
||
size
==
0
){
tscError
(
"json error invalide value"
);
retCode
=
tscSQLSyntaxErrMsg
(
errMsg
,
"json error invalide value"
,
NULL
);
goto
end
;
}
int
jsonIndex
=
startColId
++
;
for
(
int
i
=
0
;
i
<
size
;
i
++
)
{
cJSON
*
item
=
cJSON_GetArrayItem
(
root
,
i
);
if
(
!
item
)
{
tscError
(
"json inner error:%d"
,
i
);
retCode
=
tscSQLSyntaxErrMsg
(
errMsg
,
"json inner error"
,
NULL
);
goto
end
;
}
char
tagVal
[
TSDB_MAX_TAGS_LEN
];
int32_t
output
=
0
;
if
(
!
taosMbsToUcs4
(
item
->
string
,
strlen
(
item
->
string
),
varDataVal
(
tagVal
),
TSDB_MAX_TAGS_LEN
-
VARSTR_HEADER_SIZE
,
&
output
))
{
tscError
(
"json string error:%s|%s"
,
strerror
(
errno
),
item
->
string
);
retCode
=
tscSQLSyntaxErrMsg
(
errMsg
,
"serizelize json error"
,
NULL
);
goto
end
;
}
varDataSetLen
(
tagVal
,
output
);
tdAddColToKVRow
(
kvRowBuilder
,
jsonIndex
++
,
TSDB_DATA_TYPE_NCHAR
,
tagVal
);
memset
(
tagVal
,
0
,
TSDB_MAX_TAGS_LEN
);
if
(
item
->
type
==
cJSON_String
){
output
=
0
;
if
(
!
taosMbsToUcs4
(
item
->
valuestring
,
strlen
(
item
->
valuestring
),
varDataVal
(
tagVal
),
TSDB_MAX_TAGS_LEN
-
VARSTR_HEADER_SIZE
,
&
output
))
{
tscError
(
"json string error:%s|%s"
,
strerror
(
errno
),
item
->
string
);
retCode
=
tscSQLSyntaxErrMsg
(
errMsg
,
"serizelize json error"
,
NULL
);
goto
end
;
}
varDataSetLen
(
tagVal
,
output
);
tdAddColToKVRow
(
kvRowBuilder
,
jsonIndex
++
,
TSDB_DATA_TYPE_NCHAR
,
tagVal
);
}
else
if
(
item
->
type
==
cJSON_Number
){
*
((
double
*
)
tagVal
)
=
item
->
valuedouble
;
tdAddColToKVRow
(
kvRowBuilder
,
jsonIndex
++
,
TSDB_DATA_TYPE_BIGINT
,
tagVal
);
}
else
{
retCode
=
tscSQLSyntaxErrMsg
(
errMsg
,
"invalidate json value"
,
NULL
);
goto
end
;
}
}
end:
cJSON_Delete
(
root
);
return
retCode
;
}
src/common/src/tname.c
浏览文件 @
95fc626c
...
...
@@ -193,7 +193,7 @@ static bool doValidateSchema(SSchema* pSchema, int32_t numOfCols, int32_t maxLen
return
false
;
}
// 1. valid types
if
(
!
isValidDataType
(
pSchema
[
i
].
type
)
&&
pSchema
[
i
].
type
!=
TSDB_DATA_TYPE_JSON
)
{
if
(
!
isValidDataType
(
pSchema
[
i
].
type
))
{
return
false
;
}
...
...
src/common/src/ttypes.c
浏览文件 @
95fc626c
...
...
@@ -430,7 +430,7 @@ FORCE_INLINE void* getDataMax(int32_t type) {
bool
isValidDataType
(
int32_t
type
)
{
return
type
>=
TSDB_DATA_TYPE_NULL
&&
type
<=
TSDB_DATA_TYPE_
UBIGINT
;
return
type
>=
TSDB_DATA_TYPE_NULL
&&
type
<=
TSDB_DATA_TYPE_
JSON
;
}
void
setVardataNull
(
void
*
val
,
int32_t
type
)
{
...
...
src/tsdb/inc/tsdbMeta.h
浏览文件 @
95fc626c
...
...
@@ -28,6 +28,7 @@ typedef struct STable {
STSchema
*
tagSchema
;
SKVRow
tagVal
;
SSkipList
*
pIndex
;
// For TSDB_SUPER_TABLE, it is the skiplist index
SHashObj
*
jsonKeyMap
;
// For json tag key {"key":[t1, t2, t3]}
void
*
eventHandler
;
// TODO
void
*
streamHandler
;
// TODO
TSKEY
lastKey
;
...
...
src/tsdb/src/tsdbMeta.c
浏览文件 @
95fc626c
...
...
@@ -13,6 +13,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tsdbint.h"
#include "tcompare.h"
#define TSDB_SUPER_TABLE_SL_LEVEL 5
#define DEFAULT_TAG_INDEX_COLUMN 0
...
...
@@ -1051,7 +1052,40 @@ static int tsdbAddTableIntoIndex(STsdbMeta *pMeta, STable *pTable, bool refSuper
pTable
->
pSuper
=
pSTable
;
tSkipListPut
(
pSTable
->
pIndex
,
(
void
*
)
pTable
);
if
(
pSTable
->
tagSchema
->
numOfCols
==
1
&&
pSTable
->
tagSchema
->
columns
[
0
].
type
==
TSDB_DATA_TYPE_JSON
){
int16_t
nCols
=
kvRowNCols
(
pTable
->
tagVal
);
ASSERT
(
nCols
%
2
==
1
);
for
(
int
j
=
0
;
j
<
nCols
;
++
j
)
{
SColIdx
*
pColIdx
=
kvRowColIdxAt
(
pTable
->
tagVal
,
j
);
void
*
val
=
(
kvRowColVal
(
pTable
->
tagVal
,
pColIdx
));
if
(
j
==
0
){
// json value is the first
int8_t
jsonVal
=
*
(
int8_t
*
)
val
;
ASSERT
(
jsonVal
==
TSDB_DATA_BINARY_PLACEHOLDER
);
continue
;
}
void
*
tablist
=
taosHashGet
(
pSTable
->
jsonKeyMap
,
varDataVal
(
val
)
,
varDataLen
(
val
));
if
(
tablist
==
NULL
)
{
tablist
=
taosArrayInit
(
8
,
sizeof
(
uint64_t
));
if
(
tablist
==
NULL
){
terrno
=
TSDB_CODE_TDB_OUT_OF_MEMORY
;
tsdbError
(
"out of memory when alloc json tag array"
);
return
-
1
;
}
}
taosArrayPush
(
tablist
,
&
TABLE_UID
(
pTable
));
taosArraySort
(
tablist
,
compareUint64Val
);
taosArrayRemoveDuplicate
(
tablist
,
compareUint64Val
,
NULL
);
if
(
taosHashPut
(
pSTable
->
jsonKeyMap
,
varDataVal
(
val
)
,
varDataLen
(
val
),
tablist
,
sizeof
(
void
*
))
<
0
){
terrno
=
TSDB_CODE_TDB_OUT_OF_MEMORY
;
tsdbError
(
"out of memory when put json tag array"
);
return
-
1
;
}
}
}
else
{
tSkipListPut
(
pSTable
->
pIndex
,
(
void
*
)
pTable
);
}
if
(
refSuper
)
T_REF_INC
(
pSTable
);
return
0
;
...
...
@@ -1316,12 +1350,17 @@ static void *tsdbDecodeTable(void *buf, STable **pRTable) {
if
(
TABLE_TYPE
(
pTable
)
==
TSDB_SUPER_TABLE
)
{
buf
=
tdDecodeSchema
(
buf
,
&
(
pTable
->
tagSchema
));
STColumn
*
pCol
=
schemaColAt
(
pTable
->
tagSchema
,
DEFAULT_TAG_INDEX_COLUMN
);
pTable
->
pIndex
=
tSkipListCreate
(
TSDB_SUPER_TABLE_SL_LEVEL
,
colType
(
pCol
),
(
uint8_t
)(
colBytes
(
pCol
)),
NULL
,
if
(
pCol
->
type
==
TSDB_DATA_TYPE_JSON
){
assert
(
pTable
->
tagSchema
->
numOfCols
==
1
);
pTable
->
jsonKeyMap
=
taosHashInit
(
8
,
taosGetDefaultHashFunction
(
TSDB_DATA_TYPE_BINARY
),
true
,
true
);
}
else
{
pTable
->
pIndex
=
tSkipListCreate
(
TSDB_SUPER_TABLE_SL_LEVEL
,
colType
(
pCol
),
(
uint8_t
)(
colBytes
(
pCol
)),
NULL
,
SL_ALLOW_DUP_KEY
,
getTagIndexKey
);
if
(
pTable
->
pIndex
==
NULL
)
{
terrno
=
TSDB_CODE_TDB_OUT_OF_MEMORY
;
tsdbFreeTable
(
pTable
);
return
NULL
;
if
(
pTable
->
pIndex
==
NULL
)
{
terrno
=
TSDB_CODE_TDB_OUT_OF_MEMORY
;
tsdbFreeTable
(
pTable
);
return
NULL
;
}
}
}
...
...
src/util/inc/hash.h
浏览文件 @
95fc626c
...
...
@@ -88,6 +88,8 @@ SHashObj *taosHashInit(size_t capacity, _hash_fn_t fn, bool update, SHashLockTyp
*/
void
taosHashSetEqualFp
(
SHashObj
*
pHashObj
,
_equal_fn_t
fp
);
void
taosHashSetFreeFp
(
SHashObj
*
pHashObj
,
_hash_free_fn_t
fp
);
/**
* return the size of hash table
* @param pHashObj
...
...
src/util/src/hash.c
浏览文件 @
95fc626c
...
...
@@ -203,7 +203,13 @@ void taosHashSetEqualFp(SHashObj *pHashObj, _equal_fn_t fp) {
if
(
pHashObj
!=
NULL
&&
fp
!=
NULL
)
{
pHashObj
->
equalFp
=
fp
;
}
}
}
void
taosHashSetFreeFp
(
SHashObj
*
pHashObj
,
_hash_free_fn_t
fp
)
{
if
(
pHashObj
!=
NULL
&&
fp
!=
NULL
)
{
pHashObj
->
freeFp
=
fp
;
}
}
int32_t
taosHashGetSize
(
const
SHashObj
*
pHashObj
)
{
if
(
!
pHashObj
)
{
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录