Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
taosdata
TDengine
提交
c7a7cf87
T
TDengine
项目概览
taosdata
/
TDengine
1 年多 前同步成功
通知
1185
Star
22016
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看板
提交
c7a7cf87
编写于
10月 29, 2021
作者:
H
Haojun Liao
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
[td-10564] add test planner.
上级
7544dfbb
变更
12
显示空白变更内容
内联
并排
Showing
12 changed file
with
119 addition
and
33 deletion
+119
-33
include/libs/function/function.h
include/libs/function/function.h
+2
-1
include/libs/planner/planner.h
include/libs/planner/planner.h
+1
-1
source/libs/catalog/src/catalog.c
source/libs/catalog/src/catalog.c
+1
-1
source/libs/function/inc/texpr.h
source/libs/function/inc/texpr.h
+0
-1
source/libs/function/inc/tscalarfunction.h
source/libs/function/inc/tscalarfunction.h
+1
-0
source/libs/parser/src/astValidate.c
source/libs/parser/src/astValidate.c
+6
-9
source/libs/parser/src/queryInfoUtil.c
source/libs/parser/src/queryInfoUtil.c
+1
-1
source/libs/parser/test/CMakeLists.txt
source/libs/parser/test/CMakeLists.txt
+1
-1
source/libs/parser/test/parserTests.cpp
source/libs/parser/test/parserTests.cpp
+0
-1
source/libs/planner/CMakeLists.txt
source/libs/planner/CMakeLists.txt
+3
-1
source/libs/planner/src/planner.c
source/libs/planner/src/planner.c
+15
-16
source/libs/planner/test/plannerTests.cpp
source/libs/planner/test/plannerTests.cpp
+88
-0
未找到文件。
include/libs/function/function.h
浏览文件 @
c7a7cf87
...
@@ -186,7 +186,6 @@ typedef struct SResultDataInfo {
...
@@ -186,7 +186,6 @@ typedef struct SResultDataInfo {
int32_t
intermediateBytes
;
int32_t
intermediateBytes
;
}
SResultDataInfo
;
}
SResultDataInfo
;
typedef
struct
SMultiFunctionsDesc
{
typedef
struct
SMultiFunctionsDesc
{
bool
stableQuery
;
bool
stableQuery
;
bool
groupbyColumn
;
bool
groupbyColumn
;
...
@@ -224,6 +223,8 @@ const char* qGetFunctionName(int32_t functionId);
...
@@ -224,6 +223,8 @@ const char* qGetFunctionName(int32_t functionId);
void
extractFunctionDesc
(
SArray
*
pFunctionIdList
,
SMultiFunctionsDesc
*
pDesc
);
void
extractFunctionDesc
(
SArray
*
pFunctionIdList
,
SMultiFunctionsDesc
*
pDesc
);
tExprNode
*
exprdup
(
tExprNode
*
pTree
);
#ifdef __cplusplus
#ifdef __cplusplus
}
}
#endif
#endif
...
...
include/libs/planner/planner.h
浏览文件 @
c7a7cf87
...
@@ -56,7 +56,7 @@ int32_t qOptimizeQueryPlan(struct SQueryPlanNode* pQueryNode);
...
@@ -56,7 +56,7 @@ int32_t qOptimizeQueryPlan(struct SQueryPlanNode* pQueryNode);
* @param pQueryNode
* @param pQueryNode
* @return
* @return
*/
*/
int32_t
qCreateQueryPlan
(
const
struct
SQueryStmtInfo
*
pQueryInfo
,
struct
SQueryPlanNode
*
pQueryNode
);
int32_t
qCreateQueryPlan
(
const
struct
SQueryStmtInfo
*
pQueryInfo
,
struct
SQueryPlanNode
*
*
pQueryNode
);
/**
/**
* Convert the query plan to string, in order to display it in the shell.
* Convert the query plan to string, in order to display it in the shell.
...
...
source/libs/catalog/src/catalog.c
浏览文件 @
c7a7cf87
...
@@ -16,7 +16,7 @@
...
@@ -16,7 +16,7 @@
#include "catalogInt.h"
#include "catalogInt.h"
struct
SCatalog
*
getCatalogHandle
(
const
SEpSet
*
pMgmtEps
)
{
struct
SCatalog
*
getCatalogHandle
(
const
SEpSet
*
pMgmtEps
)
{
return
NULL
;
return
(
struct
SCatalog
*
)
0x1
;
}
}
int32_t
catalogGetMetaData
(
struct
SCatalog
*
pCatalog
,
const
SMetaReq
*
pMetaReq
,
SMetaData
*
pMetaData
)
{
int32_t
catalogGetMetaData
(
struct
SCatalog
*
pCatalog
,
const
SMetaReq
*
pMetaReq
,
SMetaData
*
pMetaData
)
{
...
...
source/libs/function/inc/texpr.h
浏览文件 @
c7a7cf87
...
@@ -62,7 +62,6 @@ typedef struct SExprTraverseSupp {
...
@@ -62,7 +62,6 @@ typedef struct SExprTraverseSupp {
tExprNode
*
exprTreeFromBinary
(
const
void
*
data
,
size_t
size
);
tExprNode
*
exprTreeFromBinary
(
const
void
*
data
,
size_t
size
);
tExprNode
*
exprTreeFromTableName
(
const
char
*
tbnameCond
);
tExprNode
*
exprTreeFromTableName
(
const
char
*
tbnameCond
);
tExprNode
*
exprdup
(
tExprNode
*
pTree
);
bool
exprTreeApplyFilter
(
tExprNode
*
pExpr
,
const
void
*
pItem
,
SExprTraverseSupp
*
param
);
bool
exprTreeApplyFilter
(
tExprNode
*
pExpr
,
const
void
*
pItem
,
SExprTraverseSupp
*
param
);
...
...
source/libs/function/inc/tscalarfunction.h
浏览文件 @
c7a7cf87
...
@@ -28,6 +28,7 @@ extern struct SScalarFunctionInfo scalarFunc[1];
...
@@ -28,6 +28,7 @@ extern struct SScalarFunctionInfo scalarFunc[1];
#define FUNCTION_ROUND 40
#define FUNCTION_ROUND 40
#define FUNCTION_MAVG 41
#define FUNCTION_MAVG 41
#define FUNCTION_CSUM 42
#define FUNCTION_CSUM 42
#define FUNCCTION_CONCAT 43
#ifdef __cplusplus
#ifdef __cplusplus
}
}
...
...
source/libs/parser/src/astValidate.c
浏览文件 @
c7a7cf87
...
@@ -486,6 +486,12 @@ int32_t validateGroupbyNode(SQueryStmtInfo* pQueryInfo, SArray* pList, SMsgBuf*
...
@@ -486,6 +486,12 @@ int32_t validateGroupbyNode(SQueryStmtInfo* pQueryInfo, SArray* pList, SMsgBuf*
const
char
*
msg7
=
"normal column and tags can not be mixed up in group by clause"
;
const
char
*
msg7
=
"normal column and tags can not be mixed up in group by clause"
;
const
char
*
msg8
=
"normal column can only locate at the end of group by clause"
;
const
char
*
msg8
=
"normal column can only locate at the end of group by clause"
;
SGroupbyExpr
*
pGroupExpr
=
&
(
pQueryInfo
->
groupbyExpr
);
pGroupExpr
->
columnInfo
=
taosArrayInit
(
4
,
sizeof
(
SColIndex
));
if
(
pGroupExpr
->
columnInfo
==
NULL
)
{
return
TSDB_CODE_TSC_OUT_OF_MEMORY
;
}
// todo : handle two tables situation
// todo : handle two tables situation
STableMetaInfo
*
pTableMetaInfo
=
NULL
;
STableMetaInfo
*
pTableMetaInfo
=
NULL
;
if
(
pList
==
NULL
)
{
if
(
pList
==
NULL
)
{
...
@@ -496,12 +502,6 @@ int32_t validateGroupbyNode(SQueryStmtInfo* pQueryInfo, SArray* pList, SMsgBuf*
...
@@ -496,12 +502,6 @@ int32_t validateGroupbyNode(SQueryStmtInfo* pQueryInfo, SArray* pList, SMsgBuf*
return
buildInvalidOperationMsg
(
pMsgBuf
,
msg4
);
return
buildInvalidOperationMsg
(
pMsgBuf
,
msg4
);
}
}
SGroupbyExpr
*
pGroupExpr
=
&
(
pQueryInfo
->
groupbyExpr
);
pGroupExpr
->
columnInfo
=
taosArrayInit
(
4
,
sizeof
(
SColIndex
));
if
(
pGroupExpr
->
columnInfo
==
NULL
)
{
return
TSDB_CODE_TSC_OUT_OF_MEMORY
;
}
size_t
num
=
taosArrayGetSize
(
pList
);
size_t
num
=
taosArrayGetSize
(
pList
);
if
(
num
>
TSDB_MAX_TAGS
)
{
if
(
num
>
TSDB_MAX_TAGS
)
{
return
buildInvalidOperationMsg
(
pMsgBuf
,
msg1
);
return
buildInvalidOperationMsg
(
pMsgBuf
,
msg1
);
...
@@ -1856,9 +1856,6 @@ static int32_t doAddAllColumnExprInSelectClause(SQueryStmtInfo *pQueryInfo, STab
...
@@ -1856,9 +1856,6 @@ static int32_t doAddAllColumnExprInSelectClause(SQueryStmtInfo *pQueryInfo, STab
}
}
}
}
static
int32_t
extractFunctionParameterInfo
(
SQueryStmtInfo
*
pQueryInfo
,
int32_t
tokenId
,
STableMetaInfo
**
pTableMetaInfo
,
SSchema
*
columnSchema
,
tExprNode
**
pNode
,
SColumnIndex
*
pIndex
,
tSqlExprItem
*
pParamElem
,
SMsgBuf
*
pMsgBuf
);
static
int32_t
doHandleOneParam
(
SQueryStmtInfo
*
pQueryInfo
,
tSqlExprItem
*
pItem
,
tSqlExprItem
*
pParamElem
,
int32_t
functionId
,
static
int32_t
doHandleOneParam
(
SQueryStmtInfo
*
pQueryInfo
,
tSqlExprItem
*
pItem
,
tSqlExprItem
*
pParamElem
,
int32_t
functionId
,
int32_t
*
outputIndex
,
bool
finalResult
,
SMsgBuf
*
pMsgBuf
)
{
int32_t
*
outputIndex
,
bool
finalResult
,
SMsgBuf
*
pMsgBuf
)
{
const
char
*
msg3
=
"illegal column name"
;
const
char
*
msg3
=
"illegal column name"
;
...
...
source/libs/parser/src/queryInfoUtil.c
浏览文件 @
c7a7cf87
...
@@ -212,7 +212,7 @@ void assignExprInfo(SExprInfo* dst, const SExprInfo* src) {
...
@@ -212,7 +212,7 @@ void assignExprInfo(SExprInfo* dst, const SExprInfo* src) {
}
}
#endif
#endif
//
dst->pExpr = exprdup(src->pExpr);
dst
->
pExpr
=
exprdup
(
src
->
pExpr
);
memset
(
dst
->
base
.
param
,
0
,
sizeof
(
SVariant
)
*
tListLen
(
dst
->
base
.
param
));
memset
(
dst
->
base
.
param
,
0
,
sizeof
(
SVariant
)
*
tListLen
(
dst
->
base
.
param
));
for
(
int32_t
j
=
0
;
j
<
src
->
base
.
numOfParams
;
++
j
)
{
for
(
int32_t
j
=
0
;
j
<
src
->
base
.
numOfParams
;
++
j
)
{
taosVariantAssign
(
&
dst
->
base
.
param
[
j
],
&
src
->
base
.
param
[
j
]);
taosVariantAssign
(
&
dst
->
base
.
param
[
j
],
&
src
->
base
.
param
[
j
]);
...
...
source/libs/parser/test/CMakeLists.txt
浏览文件 @
c7a7cf87
...
@@ -8,7 +8,7 @@ AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR} SOURCE_LIST)
...
@@ -8,7 +8,7 @@ AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR} SOURCE_LIST)
ADD_EXECUTABLE
(
parserTest
${
SOURCE_LIST
}
)
ADD_EXECUTABLE
(
parserTest
${
SOURCE_LIST
}
)
TARGET_LINK_LIBRARIES
(
TARGET_LINK_LIBRARIES
(
parserTest
parserTest
PUBLIC os util common parser catalog transport gtest function
PUBLIC os util common parser catalog transport gtest function
planner
)
)
TARGET_INCLUDE_DIRECTORIES
(
TARGET_INCLUDE_DIRECTORIES
(
...
...
source/libs/parser/test/parserTests.cpp
浏览文件 @
c7a7cf87
...
@@ -13,7 +13,6 @@
...
@@ -13,7 +13,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
*/
#include <function.h>
#include <gtest/gtest.h>
#include <gtest/gtest.h>
#include <iostream>
#include <iostream>
#pragma GCC diagnostic ignored "-Wwrite-strings"
#pragma GCC diagnostic ignored "-Wwrite-strings"
...
...
source/libs/planner/CMakeLists.txt
浏览文件 @
c7a7cf87
...
@@ -10,3 +10,5 @@ target_link_libraries(
...
@@ -10,3 +10,5 @@ target_link_libraries(
planner
planner
PRIVATE os util common catalog parser transport function
PRIVATE os util common catalog parser transport function
)
)
ADD_SUBDIRECTORY
(
test
)
\ No newline at end of file
source/libs/planner/src/planner.c
浏览文件 @
c7a7cf87
...
@@ -46,23 +46,20 @@ typedef struct SJoinCond {
...
@@ -46,23 +46,20 @@ typedef struct SJoinCond {
static
SArray
*
createQueryPlanImpl
(
SQueryStmtInfo
*
pQueryInfo
);
static
SArray
*
createQueryPlanImpl
(
SQueryStmtInfo
*
pQueryInfo
);
static
void
doDestroyQueryNode
(
SQueryPlanNode
*
pQueryNode
);
static
void
doDestroyQueryNode
(
SQueryPlanNode
*
pQueryNode
);
int32_t
qOptimizeQueryPlan
(
struct
SQueryPlanNode
*
pQueryNode
)
{
int32_t
qOptimizeQueryPlan
(
struct
SQueryPlanNode
*
pQueryNode
)
{
return
0
;
return
0
;
}
}
int32_t
qCreateQueryPlan
(
const
struct
SQueryStmtInfo
*
pQueryInfo
,
struct
SQueryPlanNode
*
pQueryNode
)
{
int32_t
qCreateQueryPlan
(
const
struct
SQueryStmtInfo
*
pQueryInfo
,
struct
SQueryPlanNode
*
*
pQueryNode
)
{
SArray
*
upstream
=
createQueryPlanImpl
((
struct
SQueryStmtInfo
*
)
pQueryInfo
);
SArray
*
upstream
=
createQueryPlanImpl
((
struct
SQueryStmtInfo
*
)
pQueryInfo
);
assert
(
taosArrayGetSize
(
upstream
)
==
1
);
assert
(
taosArrayGetSize
(
upstream
)
==
1
);
/*SQueryPlanNode* p = */
taosArrayGetP
(
upstream
,
0
);
*
pQueryNode
=
taosArrayGetP
(
upstream
,
0
);
taosArrayDestroy
(
upstream
);
taosArrayDestroy
(
upstream
);
return
TSDB_CODE_SUCCESS
;
return
TSDB_CODE_SUCCESS
;
}
}
int32_t
qQueryPlanToString
(
struct
SQueryPlanNode
*
pQueryNode
,
char
**
str
)
{
return
0
;
}
int32_t
qQueryPlanToSql
(
struct
SQueryPlanNode
*
pQueryNode
,
char
**
sql
)
{
int32_t
qQueryPlanToSql
(
struct
SQueryPlanNode
*
pQueryNode
,
char
**
sql
)
{
return
0
;
return
0
;
}
}
...
@@ -108,10 +105,12 @@ static SQueryPlanNode* createQueryNode(int32_t type, const char* name, SQueryPla
...
@@ -108,10 +105,12 @@ static SQueryPlanNode* createQueryNode(int32_t type, const char* name, SQueryPla
}
}
pNode
->
numOfOutput
=
numOfOutput
;
pNode
->
numOfOutput
=
numOfOutput
;
pNode
->
pExpr
=
calloc
(
numOfOutput
,
sizeof
(
SExprInfo
));
pNode
->
pExpr
=
taosArrayInit
(
numOfOutput
,
POINTER_BYTES
);
for
(
int32_t
i
=
0
;
i
<
numOfOutput
;
++
i
)
{
for
(
int32_t
i
=
0
;
i
<
numOfOutput
;
++
i
)
{
SExprInfo
*
pExprInfo
=
taosArrayGet
(
pNode
->
pExpr
,
i
);
SExprInfo
*
p
=
calloc
(
1
,
sizeof
(
SExprInfo
));
assignExprInfo
(
pExprInfo
,
pExpr
[
i
]);
assignExprInfo
(
p
,
pExpr
[
i
]);
taosArrayPush
(
pNode
->
pExpr
,
&
p
);
}
}
pNode
->
pPrevNodes
=
taosArrayInit
(
4
,
POINTER_BYTES
);
pNode
->
pPrevNodes
=
taosArrayInit
(
4
,
POINTER_BYTES
);
...
@@ -199,7 +198,6 @@ static SQueryPlanNode* doAddTableColumnNode(SQueryStmtInfo* pQueryInfo, STableMe
...
@@ -199,7 +198,6 @@ static SQueryPlanNode* doAddTableColumnNode(SQueryStmtInfo* pQueryInfo, STableMe
}
}
pNode
=
createQueryNode
(
QNODE_PROJECT
,
"Projection"
,
&
pNode
,
1
,
pExpr
,
numOfCols
,
info
,
NULL
);
pNode
=
createQueryNode
(
QNODE_PROJECT
,
"Projection"
,
&
pNode
,
1
,
pExpr
,
numOfCols
,
info
,
NULL
);
// dropAllExprInfo(pExpr);
tfree
(
pExpr
);
tfree
(
pExpr
);
}
}
...
@@ -568,14 +566,15 @@ int32_t queryPlanToStringImpl(char* buf, SQueryPlanNode* pQueryNode, int32_t lev
...
@@ -568,14 +566,15 @@ int32_t queryPlanToStringImpl(char* buf, SQueryPlanNode* pQueryNode, int32_t lev
return
len
;
return
len
;
}
}
char
*
queryPlanToString
(
SQueryPlanNode
*
pQueryNode
)
{
int32_t
qQueryPlanToString
(
struct
SQueryPlanNode
*
pQueryNode
,
char
**
str
)
{
assert
(
pQueryNode
);
assert
(
pQueryNode
);
char
*
buf
=
calloc
(
1
,
4096
);
*
str
=
calloc
(
1
,
4096
);
int32_t
len
=
sprintf
(
buf
,
"===== logic plan =====
\n
"
);
int32_t
len
=
sprintf
(
*
str
,
"===== logic plan =====
\n
"
);
queryPlanToStringImpl
(
buf
,
pQueryNode
,
0
,
len
);
queryPlanToStringImpl
(
*
str
,
pQueryNode
,
0
,
len
);
return
buf
;
return
TSDB_CODE_SUCCESS
;
}
}
SQueryPlanNode
*
queryPlanFromString
()
{
SQueryPlanNode
*
queryPlanFromString
()
{
...
...
source/libs/planner/test/plannerTests.cpp
浏览文件 @
c7a7cf87
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* This program is free software: you can use, redistribute, and/or modify
* it under the terms of the GNU Affero General Public License, version 3
* or later ("AGPL"), as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <gtest/gtest.h>
#include <iostream>
#include "os.h"
#include "taos.h"
#include "parser.h"
#pragma GCC diagnostic ignored "-Wwrite-strings"
#pragma GCC diagnostic ignored "-Wunused-function"
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Wsign-compare"
int
main
(
int
argc
,
char
**
argv
)
{
testing
::
InitGoogleTest
(
&
argc
,
argv
);
return
RUN_ALL_TESTS
();
}
TEST
(
testCase
,
planner_test
)
{
char
msg
[
128
]
=
{
0
};
const
char
*
sql
=
"select top(a*b / 99, 20) from `t.1abc` interval(10s, 1s)"
;
SQueryStmtInfo
*
pQueryInfo
=
nullptr
;
// int32_t code = qParseQuerySql(sql, strlen(sql), &pQueryInfo, 0, msg, sizeof(msg));
// ASSERT_EQ(code, 0);
// SSqlNode* pNode = (SSqlNode*)taosArrayGetP(((SArray*)info1.list), 0);
// int32_t code = evaluateSqlNode(pNode, TSDB_TIME_PRECISION_NANO, &buf);
// ASSERT_EQ(code, 0);
//
// SMetaReq req = {0};
// int32_t ret = qParserExtractRequestedMetaInfo(&info1, &req, msg, 128);
// ASSERT_EQ(ret, 0);
// ASSERT_EQ(taosArrayGetSize(req.pTableName), 1);
//
// SQueryStmtInfo* pQueryInfo = createQueryInfo();
// setTableMetaInfo(pQueryInfo, &req);
//
// SSqlNode* pSqlNode = (SSqlNode*)taosArrayGetP(info1.list, 0);
// ret = validateSqlNode(pSqlNode, pQueryInfo, &buf);
// ASSERT_EQ(ret, 0);
//
// SArray* pExprList = pQueryInfo->exprList;
// ASSERT_EQ(taosArrayGetSize(pExprList), 2);
//
// SExprInfo* p1 = (SExprInfo*)taosArrayGetP(pExprList, 1);
// ASSERT_EQ(p1->base.uid, 110);
// ASSERT_EQ(p1->base.numOfParams, 1);
// ASSERT_EQ(p1->base.resSchema.type, TSDB_DATA_TYPE_DOUBLE);
// ASSERT_STRCASEEQ(p1->base.resSchema.name, "top(a*b / 99, 20)");
// ASSERT_EQ(p1->base.colInfo.flag, TSDB_COL_NORMAL);
// ASSERT_STRCASEEQ(p1->base.token, "top(a*b / 99, 20)");
// ASSERT_EQ(p1->base.interBytes, 16);
//
// ASSERT_EQ(p1->pExpr->nodeType, TEXPR_UNARYEXPR_NODE);
// ASSERT_EQ(p1->pExpr->_node.functionId, FUNCTION_TOP);
// ASSERT_TRUE(p1->pExpr->_node.pRight == NULL);
//
// tExprNode* pParam = p1->pExpr->_node.pLeft;
//
// ASSERT_EQ(pParam->nodeType, TEXPR_BINARYEXPR_NODE);
// ASSERT_EQ(pParam->_node.optr, TSDB_BINARY_OP_DIVIDE);
// ASSERT_EQ(pParam->_node.pLeft->nodeType, TEXPR_BINARYEXPR_NODE);
// ASSERT_EQ(pParam->_node.pRight->nodeType, TEXPR_VALUE_NODE);
//
// ASSERT_EQ(taosArrayGetSize(pQueryInfo->colList), 3);
// ASSERT_EQ(pQueryInfo->fieldsInfo.numOfOutput, 2);
//
// destroyQueryInfo(pQueryInfo);
// qParserClearupMetaRequestInfo(&req);
// destroySqlInfo(&info1);
}
\ No newline at end of file
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录