diff --git a/src/client/src/tscGlobalmerge.c b/src/client/src/tscGlobalmerge.c index 14ab595c8da18f82dcdd4e336ecc2fc03f5654fe..36a8cea774600ffa5a7082973ae3700874dc8395 100644 --- a/src/client/src/tscGlobalmerge.c +++ b/src/client/src/tscGlobalmerge.c @@ -1021,7 +1021,13 @@ static void ensureOutputBuf(SSLimitOperatorInfo * pInfo, SSDataBlock *pResultBlo } } -static void doSlimitImpl(SOperatorInfo* pOperator, SSLimitOperatorInfo* pInfo, SSDataBlock* pBlock) { +enum { + BLOCK_NEW_GROUP = 1, + BLOCK_NO_GROUP = 2, + BLOCK_SAME_GROUP = 3, +}; + +static int32_t doSlimitImpl(SOperatorInfo* pOperator, SSLimitOperatorInfo* pInfo, SSDataBlock* pBlock) { int32_t rowIndex = 0; while (rowIndex < pBlock->info.rows) { @@ -1030,12 +1036,12 @@ static void doSlimitImpl(SOperatorInfo* pOperator, SSLimitOperatorInfo* pInfo, S bool samegroup = true; if (pInfo->hasPrev) { for (int32_t i = 0; i < numOfCols; ++i) { - SColIndex * pIndex = taosArrayGet(pInfo->orderColumnList, i); + SColIndex *pIndex = taosArrayGet(pInfo->orderColumnList, i); SColumnInfoData *pColInfoData = taosArrayGet(pBlock->pDataBlock, pIndex->colIndex); SColumnInfo *pColInfo = &pColInfoData->info; - char * d = rowIndex * pColInfo->bytes + (char *)pColInfoData->pData; + char *d = rowIndex * pColInfo->bytes + (char *)pColInfoData->pData; int32_t ret = columnValueAscendingComparator(pInfo->prevRow[i], d, pColInfo->type, pColInfo->bytes); if (ret != 0) { // it is a new group samegroup = false; @@ -1063,10 +1069,17 @@ static void doSlimitImpl(SOperatorInfo* pOperator, SSLimitOperatorInfo* pInfo, S if (pInfo->slimit.limit >= 0 && pInfo->groupTotal >= pInfo->slimit.limit) { setQueryStatus(pOperator->pRuntimeEnv, QUERY_COMPLETED); pOperator->status = OP_EXEC_DONE; - return; + return BLOCK_NO_GROUP; } pInfo->groupTotal += 1; + + // data in current group not allowed, return if current result does not belong to the previous group.And there + // are results exists in current SSDataBlock + if (!pInfo->multigroupResult && !samegroup && pInfo->pRes->info.rows > 0) { + return BLOCK_NEW_GROUP; + } + doHandleDataInCurrentGroup(pInfo, pBlock, rowIndex); } else { // handle the offset in the same group @@ -1081,6 +1094,8 @@ static void doSlimitImpl(SOperatorInfo* pOperator, SSLimitOperatorInfo* pInfo, S rowIndex += 1; } + + return BLOCK_SAME_GROUP; } SSDataBlock* doSLimit(void* param, bool* newgroup) { @@ -1092,6 +1107,14 @@ SSDataBlock* doSLimit(void* param, bool* newgroup) { SSLimitOperatorInfo *pInfo = pOperator->info; pInfo->pRes->info.rows = 0; + if (pInfo->pPrevBlock != NULL) { + ensureOutputBuf(pInfo, pInfo->pRes, pInfo->pPrevBlock->info.rows); + int32_t ret = doSlimitImpl(pOperator, pInfo, pInfo->pPrevBlock); + assert(ret != BLOCK_NEW_GROUP); + + pInfo->pPrevBlock = NULL; + } + assert(pInfo->currentGroupOffset >= 0); while(1) { @@ -1104,7 +1127,12 @@ SSDataBlock* doSLimit(void* param, bool* newgroup) { } ensureOutputBuf(pInfo, pInfo->pRes, pBlock->info.rows); - doSlimitImpl(pOperator, pInfo, pBlock); + int32_t ret = doSlimitImpl(pOperator, pInfo, pBlock); + if (ret == BLOCK_NEW_GROUP) { + pInfo->pPrevBlock = pBlock; + return pInfo->pRes; + } + if (pOperator->status == OP_EXEC_DONE) { return pInfo->pRes->info.rows == 0 ? NULL : pInfo->pRes; } diff --git a/src/client/src/tscSQLParser.c b/src/client/src/tscSQLParser.c index c827ae96d50705bc5b2b7b80e59ad3c0c553d2eb..629d7c8a39c357e7bc5fcdf208c5dce7e93510a5 100644 --- a/src/client/src/tscSQLParser.c +++ b/src/client/src/tscSQLParser.c @@ -926,7 +926,6 @@ int32_t tscValidateSqlInfo(SSqlObj* pSql, struct SSqlInfo* pInfo) { pQueryInfo = pCmd->active; pQueryInfo->pUdfInfo = pUdfInfo; pQueryInfo->udfCopy = true; - } } @@ -8436,6 +8435,7 @@ static int32_t doValidateSubquery(SSqlNode* pSqlNode, int32_t index, SSqlObj* pS if (taosArrayGetSize(subInfo->pSubquery) >= 2) { return invalidOperationMsg(msgBuf, "not support union in subquery"); } + SQueryInfo* pSub = calloc(1, sizeof(SQueryInfo)); tscInitQueryInfo(pSub); @@ -8458,6 +8458,7 @@ static int32_t doValidateSubquery(SSqlNode* pSqlNode, int32_t index, SSqlObj* pS if (pTableMetaInfo1 == NULL) { return TSDB_CODE_TSC_OUT_OF_MEMORY; } + pTableMetaInfo1->pTableMeta = extractTempTableMetaFromSubquery(pSub); pTableMetaInfo1->tableMetaCapacity = tscGetTableMetaSize(pTableMetaInfo1->pTableMeta); @@ -8541,7 +8542,7 @@ int32_t validateSqlNode(SSqlObj* pSql, SSqlNode* pSqlNode, SQueryInfo* pQueryInf // check if there is 3 level select SRelElementPair* subInfo = taosArrayGet(pSqlNode->from->list, i); SSqlNode* p = taosArrayGetP(subInfo->pSubquery, 0); - if (p->from->type == SQL_NODE_FROM_SUBQUERY){ + if (p->from->type == SQL_NODE_FROM_SUBQUERY) { return invalidOperationMsg(tscGetErrorMsgPayload(pCmd), msg9); } @@ -8633,6 +8634,15 @@ int32_t validateSqlNode(SSqlObj* pSql, SSqlNode* pSqlNode, SQueryInfo* pQueryInf } } + // disable group result mixed up if interval/session window query exists. + if (isTimeWindowQuery(pQueryInfo)) { + size_t num = taosArrayGetSize(pQueryInfo->pUpstream); + for(int32_t i = 0; i < num; ++i) { + SQueryInfo* pUp = taosArrayGetP(pQueryInfo->pUpstream, i); + pUp->multigroupResult = false; + } + } + // parse the having clause in the first place int32_t joinQuery = (pSqlNode->from != NULL && taosArrayGetSize(pSqlNode->from->list) > 1); if (validateHavingClause(pQueryInfo, pSqlNode->pHaving, pCmd, pSqlNode->pSelNodeList, joinQuery, timeWindowQuery) != diff --git a/src/client/src/tscUtil.c b/src/client/src/tscUtil.c index 1a953cfd3dc3bb3f50d591a6f712cfafb9ea28ed..987bf60b9954f3b9ba2077ebfe145d4515524c3a 100644 --- a/src/client/src/tscUtil.c +++ b/src/client/src/tscUtil.c @@ -3062,6 +3062,7 @@ void tscInitQueryInfo(SQueryInfo* pQueryInfo) { pQueryInfo->slimit.offset = 0; pQueryInfo->pUpstream = taosArrayInit(4, POINTER_BYTES); pQueryInfo->window = TSWINDOW_INITIALIZER; + pQueryInfo->multigroupResult = true; } int32_t tscAddQueryInfo(SSqlCmd* pCmd) { @@ -3073,7 +3074,6 @@ int32_t tscAddQueryInfo(SSqlCmd* pCmd) { } tscInitQueryInfo(pQueryInfo); - pQueryInfo->msg = pCmd->payload; // pointer to the parent error message buffer if (pCmd->pQueryInfo == NULL) { @@ -3155,6 +3155,7 @@ int32_t tscQueryInfoCopy(SQueryInfo* pQueryInfo, const SQueryInfo* pSrc) { pQueryInfo->window = pSrc->window; pQueryInfo->sessionWindow = pSrc->sessionWindow; pQueryInfo->pTableMetaInfo = NULL; + pQueryInfo->multigroupResult = pSrc->multigroupResult; pQueryInfo->bufLen = pSrc->bufLen; pQueryInfo->orderProjectQuery = pSrc->orderProjectQuery; @@ -3554,7 +3555,7 @@ SSqlObj* createSubqueryObj(SSqlObj* pSql, int16_t tableIndex, __async_cb_func_t pNewQueryInfo->pTableMetaInfo = NULL; pNewQueryInfo->bufLen = pQueryInfo->bufLen; pNewQueryInfo->buf = malloc(pQueryInfo->bufLen); - + pNewQueryInfo->multigroupResult = pQueryInfo->multigroupResult; pNewQueryInfo->distinct = pQueryInfo->distinct; if (pNewQueryInfo->buf == NULL) { @@ -3779,12 +3780,12 @@ static void tscSubqueryCompleteCallback(void* param, TAOS_RES* tres, int code) { if (code && !((code == TSDB_CODE_TDB_INVALID_TABLE_ID || code == TSDB_CODE_VND_INVALID_VGROUP_ID) && pParentSql->retry < pParentSql->maxRetry)) { pParentSql->res.code = code; - + tscAsyncResultOnError(pParentSql); return; } - tscFreeSubobj(pParentSql); + tscFreeSubobj(pParentSql); tfree(pParentSql->pSubs); pParentSql->res.code = TSDB_CODE_SUCCESS; @@ -3793,9 +3794,9 @@ static void tscSubqueryCompleteCallback(void* param, TAOS_RES* tres, int code) { tscDebug("0x%"PRIx64" retry parse sql and send query, prev error: %s, retry:%d", pParentSql->self, tstrerror(code), pParentSql->retry); - + tscResetSqlCmd(&pParentSql->cmd, true); - + code = tsParseSql(pParentSql, true); if (code == TSDB_CODE_TSC_ACTION_IN_PROGRESS) { return; @@ -3864,7 +3865,7 @@ void executeQuery(SSqlObj* pSql, SQueryInfo* pQueryInfo) { pNew->maxRetry = pSql->maxRetry; pNew->cmd.resColumnId = TSDB_RES_COL_ID; - + tsem_init(&pNew->rspSem, 0, 0); SRetrieveSupport* ps = calloc(1, sizeof(SRetrieveSupport)); // todo use object id @@ -4400,7 +4401,7 @@ int32_t tscCreateTableMetaFromSTableMeta(STableMeta** ppChild, const char* name, size_t sz = 0; STableMeta* pChild = *ppChild; STableMeta* pChild1; - + taosHashGetCloneExt(tscTableMetaMap, pChild->sTableName, strnlen(pChild->sTableName, TSDB_TABLE_FNAME_LEN), NULL, (void **)&p, &sz); // tableMeta exists, build child table meta according to the super table meta @@ -4411,9 +4412,9 @@ int32_t tscCreateTableMetaFromSTableMeta(STableMeta** ppChild, const char* name, int32_t tableMetaSize = sizeof(STableMeta) + totalBytes; if (*tableMetaCapacity < tableMetaSize) { pChild1 = realloc(pChild, tableMetaSize); - if(pChild1 == NULL) + if(pChild1 == NULL) return -1; - pChild = pChild1; + pChild = pChild1; *tableMetaCapacity = (size_t)tableMetaSize; } @@ -4685,6 +4686,7 @@ int32_t tscCreateQueryFromQueryInfo(SQueryInfo* pQueryInfo, SQueryAttr* pQueryAt pQueryAttr->distinct = pQueryInfo->distinct; pQueryAttr->sw = pQueryInfo->sessionWindow; pQueryAttr->stateWindow = pQueryInfo->stateWindow; + pQueryAttr->multigroupResult = pQueryInfo->multigroupResult; pQueryAttr->numOfCols = numOfCols; pQueryAttr->numOfOutput = numOfOutput; diff --git a/src/query/inc/qExecutor.h b/src/query/inc/qExecutor.h index b06b73e13f8abe054d6e922213eda736998870e7..7aab8dbca747f40a9f93ee826cea5f9a62a7b297 100644 --- a/src/query/inc/qExecutor.h +++ b/src/query/inc/qExecutor.h @@ -219,6 +219,7 @@ typedef struct SQueryAttr { bool distinct; // distinct query or not bool stateWindow; // window State on sub/normal table bool createFilterOperator; // if filter operator is needed + bool multigroupResult; // multigroup result can exist in one SSDataBlock int32_t interBufSize; // intermediate buffer sizse int32_t havingNum; // having expr number @@ -460,19 +461,21 @@ typedef struct SLimitOperatorInfo { } SLimitOperatorInfo; typedef struct SSLimitOperatorInfo { - int64_t groupTotal; - int64_t currentGroupOffset; - - int64_t rowsTotal; - int64_t currentOffset; - SLimitVal limit; - SLimitVal slimit; - - char **prevRow; - SArray *orderColumnList; - bool hasPrev; - bool ignoreCurrentGroup; + int64_t groupTotal; + int64_t currentGroupOffset; + + int64_t rowsTotal; + int64_t currentOffset; + SLimitVal limit; + SLimitVal slimit; + + char **prevRow; + SArray *orderColumnList; + bool hasPrev; + bool ignoreCurrentGroup; + bool multigroupResult; SSDataBlock *pRes; // result buffer + SSDataBlock *pPrevBlock; int64_t capacity; int64_t threshold; } SSLimitOperatorInfo; @@ -488,6 +491,7 @@ typedef struct SFillOperatorInfo { int64_t totalInputRows; void **p; SSDataBlock *existNewGroupBlock; + bool multigroupResult; } SFillOperatorInfo; typedef struct SGroupbyOperatorInfo { @@ -573,7 +577,7 @@ SOperatorInfo* createLimitOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperatorI SOperatorInfo* createTimeIntervalOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperatorInfo* upstream, SExprInfo* pExpr, int32_t numOfOutput); SOperatorInfo* createAllTimeIntervalOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperatorInfo* upstream, SExprInfo* pExpr, int32_t numOfOutput); SOperatorInfo* createSWindowOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperatorInfo* upstream, SExprInfo* pExpr, int32_t numOfOutput); -SOperatorInfo* createFillOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperatorInfo* upstream, SExprInfo* pExpr, int32_t numOfOutput); +SOperatorInfo* createFillOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperatorInfo* upstream, SExprInfo* pExpr, int32_t numOfOutput, bool multigroupResult); SOperatorInfo* createGroupbyOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperatorInfo* upstream, SExprInfo* pExpr, int32_t numOfOutput); SOperatorInfo* createMultiTableAggOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperatorInfo* upstream, SExprInfo* pExpr, int32_t numOfOutput); SOperatorInfo* createMultiTableTimeIntervalOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperatorInfo* upstream, SExprInfo* pExpr, int32_t numOfOutput); @@ -585,7 +589,7 @@ SOperatorInfo* createMultiwaySortOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SEx int32_t numOfRows, void* merger); SOperatorInfo* createGlobalAggregateOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperatorInfo* upstream, SExprInfo* pExpr, int32_t numOfOutput, void* param, SArray* pUdfInfo, bool groupResultMixedUp); SOperatorInfo* createStatewindowOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperatorInfo* upstream, SExprInfo* pExpr, int32_t numOfOutput); -SOperatorInfo* createSLimitOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperatorInfo* upstream, SExprInfo* pExpr, int32_t numOfOutput, void* merger); +SOperatorInfo* createSLimitOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperatorInfo* upstream, SExprInfo* pExpr, int32_t numOfOutput, void* merger, bool multigroupResult); SOperatorInfo* createFilterOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperatorInfo* upstream, SExprInfo* pExpr, int32_t numOfOutput, SColumnInfo* pCols, int32_t numOfFilter); diff --git a/src/query/inc/qTableMeta.h b/src/query/inc/qTableMeta.h index 0dae74ac8224329810064c773ecd478d4a89ad15..54de5185b02194bc7cd55a609cfee3815ace2f4f 100644 --- a/src/query/inc/qTableMeta.h +++ b/src/query/inc/qTableMeta.h @@ -148,6 +148,7 @@ typedef struct SQueryInfo { bool orderProjectQuery; bool stateWindow; bool globalMerge; + bool multigroupResult; } SQueryInfo; /** diff --git a/src/query/src/qExecutor.c b/src/query/src/qExecutor.c index 8bf8261160cb46440eeeddf120b1ed7a228a01b4..425270b36a7734bc0817b1b9e5fd8cbf6fc10725 100644 --- a/src/query/src/qExecutor.c +++ b/src/query/src/qExecutor.c @@ -2261,7 +2261,7 @@ static int32_t setupQueryRuntimeEnv(SQueryRuntimeEnv *pRuntimeEnv, int32_t numOf case OP_Fill: { SOperatorInfo* pInfo = pRuntimeEnv->proot; - pRuntimeEnv->proot = createFillOperatorInfo(pRuntimeEnv, pInfo, pInfo->pExpr, pInfo->numOfOutput); + pRuntimeEnv->proot = createFillOperatorInfo(pRuntimeEnv, pInfo, pInfo->pExpr, pInfo->numOfOutput, pQueryAttr->multigroupResult); break; } @@ -2271,16 +2271,20 @@ static int32_t setupQueryRuntimeEnv(SQueryRuntimeEnv *pRuntimeEnv, int32_t numOf } case OP_GlobalAggregate: { // If fill operator exists, the result rows of different group can not be in the same SSDataBlock. - bool groupResultMixedUp = (pQueryAttr->fillType == TSDB_FILL_NONE); + bool multigroupResult = pQueryAttr->multigroupResult; + if (pQueryAttr->multigroupResult) { + multigroupResult = (pQueryAttr->fillType == TSDB_FILL_NONE); + } + pRuntimeEnv->proot = createGlobalAggregateOperatorInfo(pRuntimeEnv, pRuntimeEnv->proot, pQueryAttr->pExpr3, - pQueryAttr->numOfExpr3, merger, pQueryAttr->pUdfInfo, groupResultMixedUp); + pQueryAttr->numOfExpr3, merger, pQueryAttr->pUdfInfo, multigroupResult); break; } case OP_SLimit: { int32_t num = pRuntimeEnv->proot->numOfOutput; SExprInfo* pExpr = pRuntimeEnv->proot->pExpr; - pRuntimeEnv->proot = createSLimitOperatorInfo(pRuntimeEnv, pRuntimeEnv->proot, pExpr, num, merger); + pRuntimeEnv->proot = createSLimitOperatorInfo(pRuntimeEnv, pRuntimeEnv->proot, pExpr, num, merger, pQueryAttr->multigroupResult); break; } @@ -6357,7 +6361,7 @@ static void doHandleRemainBlockFromNewGroup(SFillOperatorInfo *pInfo, SQueryRunt if (taosFillHasMoreResults(pInfo->pFillInfo)) { *newgroup = false; doFillTimeIntervalGapsInResults(pInfo->pFillInfo, pInfo->pRes, (int32_t)pRuntimeEnv->resultInfo.capacity, pInfo->p); - if (pInfo->pRes->info.rows > pRuntimeEnv->resultInfo.threshold) { + if (pInfo->pRes->info.rows > pRuntimeEnv->resultInfo.threshold || (!pInfo->multigroupResult)) { return; } } @@ -6389,7 +6393,7 @@ static SSDataBlock* doFill(void* param, bool* newgroup) { SQueryRuntimeEnv *pRuntimeEnv = pOperator->pRuntimeEnv; doHandleRemainBlockFromNewGroup(pInfo, pRuntimeEnv, newgroup); - if (pInfo->pRes->info.rows > pRuntimeEnv->resultInfo.threshold) { + if (pInfo->pRes->info.rows > pRuntimeEnv->resultInfo.threshold || (!pInfo->multigroupResult && pInfo->pRes->info.rows > 0)) { return pInfo->pRes; } // if (taosFillHasMoreResults(pInfo->pFillInfo)) { @@ -6426,8 +6430,8 @@ static SSDataBlock* doFill(void* param, bool* newgroup) { pInfo->existNewGroupBlock = pBlock; *newgroup = false; - // fill the previous group data block - // before handle a new data block, close the fill operation for previous group data block + // Fill the previous group data block, before handle the data block of new group. + // Close the fill operation for previous group data block taosFillSetStartInfo(pInfo->pFillInfo, 0, pRuntimeEnv->pQueryAttr->window.ekey); } else { if (pBlock == NULL) { @@ -6448,8 +6452,9 @@ static SSDataBlock* doFill(void* param, bool* newgroup) { // current group has no more result to return if (pInfo->pRes->info.rows > 0) { - // the result in current group not reach the threshold of output result, continue - if (pInfo->pRes->info.rows > pRuntimeEnv->resultInfo.threshold || pBlock == NULL) { + // 1. The result in current group not reach the threshold of output result, continue + // 2. If multiple group results existing in one SSDataBlock is not allowed, return immediately + if (pInfo->pRes->info.rows > pRuntimeEnv->resultInfo.threshold || pBlock == NULL || (!pInfo->multigroupResult)) { return pInfo->pRes; } @@ -6944,10 +6949,10 @@ SOperatorInfo* createGroupbyOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperato return pOperator; } -SOperatorInfo* createFillOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperatorInfo* upstream, SExprInfo* pExpr, - int32_t numOfOutput) { +SOperatorInfo* createFillOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperatorInfo* upstream, SExprInfo* pExpr, int32_t numOfOutput, bool multigroupResult) { SFillOperatorInfo* pInfo = calloc(1, sizeof(SFillOperatorInfo)); pInfo->pRes = createOutputBuf(pExpr, numOfOutput, pRuntimeEnv->resultInfo.capacity); + pInfo->multigroupResult = multigroupResult; { SQueryAttr* pQueryAttr = pRuntimeEnv->pQueryAttr; @@ -6983,7 +6988,7 @@ SOperatorInfo* createFillOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperatorIn return pOperator; } -SOperatorInfo* createSLimitOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperatorInfo* upstream, SExprInfo* pExpr, int32_t numOfOutput, void* pMerger) { +SOperatorInfo* createSLimitOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperatorInfo* upstream, SExprInfo* pExpr, int32_t numOfOutput, void* pMerger, bool multigroupResult) { SSLimitOperatorInfo* pInfo = calloc(1, sizeof(SSLimitOperatorInfo)); SQueryAttr* pQueryAttr = pRuntimeEnv->pQueryAttr; @@ -6995,6 +7000,7 @@ SOperatorInfo* createSLimitOperatorInfo(SQueryRuntimeEnv* pRuntimeEnv, SOperator pInfo->threshold = pInfo->capacity * 0.8; pInfo->currentOffset = pQueryAttr->limit.offset; pInfo->currentGroupOffset = pQueryAttr->slimit.offset; + pInfo->multigroupResult= multigroupResult; // TODO refactor int32_t len = 0;