/* * Copyright (c) 2019 TAOS Data, Inc. * * 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 . */ #include "geosWrapper.h" #include "parInsertUtil.h" #include "parToken.h" #include "scalar.h" #include "tglobal.h" #include "ttime.h" #include "decimal.h" // CSV delimiter and quote character definitions #define CSV_DEFAULT_DELIMITER ',' #define CSV_QUOTE_SINGLE '\'' #define CSV_QUOTE_DOUBLE '"' #define CSV_ESCAPE_CHAR '\\' #define CSV_QUOTE_NONE '\0' typedef struct SCsvParser { char delimiter; // Field delimiter (default: ',') char quote; // Quote character (default: '"') char escape; // Escape character (default: '\') bool allowNewlineInField; // Allow newlines in quoted fields char* buffer; // Read buffer size_t bufferSize; // Buffer size size_t bufferPos; // Current position in buffer size_t bufferLen; // Valid data length in buffer bool eof; // End of file reached TdFilePtr pFile; // File pointer // Line buffer for reuse to avoid frequent memory allocation char* lineBuffer; // Reusable line buffer size_t lineBufferCapacity; // Line buffer capacity } SCsvParser; typedef struct SInsertParseContext { SParseContext* pComCxt; SMsgBuf msg; char tmpTokenBuf[TSDB_MAX_BYTES_PER_ROW]; SBoundColInfo tags; // for stmt bool missCache; bool usingDuplicateTable; bool forceUpdate; bool needTableTagVal; bool needRequest; // whether or not request server bool isStmtBind; // whether is stmt bind uint8_t stmtTbNameFlag; } SInsertParseContext; typedef int32_t (*_row_append_fn_t)(SMsgBuf* pMsgBuf, const void* value, int32_t len, void* param); static int32_t parseBoundTagsClause(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt); static int32_t parseTagsClause(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, bool autoCreate); // CSV parser function declarations static int32_t csvParserInit(SCsvParser* parser, TdFilePtr pFile); static void csvParserDestroy(SCsvParser* parser); static int32_t csvParserFillBuffer(SCsvParser* parser); static int32_t csvParserReadLine(SCsvParser* parser); static void destroySavedCsvParser(SVnodeModifyOpStmt* pStmt); static int32_t csvParserExpandLineBuffer(SCsvParser* parser, size_t requiredLen); static uint8_t TRUE_VALUE = (uint8_t)TSDB_TRUE; static uint8_t FALSE_VALUE = (uint8_t)TSDB_FALSE; static FORCE_INLINE bool isNullValue(int8_t dataType, SToken* pToken) { return TK_NULL == pToken->type || (TK_NK_STRING == pToken->type && !IS_STR_DATA_TYPE(dataType) && IS_NULL_STR(pToken->z, pToken->n)); } static FORCE_INLINE int32_t toDouble(SToken* pToken, double* value, char** endPtr) { SET_ERRNO(0); *value = taosStr2Double(pToken->z, endPtr); // not a valid integer number, return error if ((*endPtr - pToken->z) != pToken->n) { return TK_NK_ILLEGAL; } return pToken->type; } static int32_t skipInsertInto(const char** pSql, SMsgBuf* pMsg) { SToken token; NEXT_TOKEN(*pSql, token); if (TK_INSERT != token.type && TK_IMPORT != token.type) { return buildSyntaxErrMsg(pMsg, "keyword INSERT is expected", token.z); } NEXT_TOKEN(*pSql, token); if (TK_INTO != token.type) { return buildSyntaxErrMsg(pMsg, "keyword INTO is expected", token.z); } return TSDB_CODE_SUCCESS; } static int32_t skipParentheses(SInsertParseContext* pCxt, const char** pSql) { SToken token; int32_t expectRightParenthesis = 1; while (1) { NEXT_TOKEN(*pSql, token); if (TK_NK_LP == token.type) { ++expectRightParenthesis; } else if (TK_NK_RP == token.type && 0 == --expectRightParenthesis) { break; } if (0 == token.n) { return buildSyntaxErrMsg(&pCxt->msg, ") expected", NULL); } } return TSDB_CODE_SUCCESS; } static int32_t skipTableOptions(SInsertParseContext* pCxt, const char** pSql) { do { int32_t index = 0; SToken token; NEXT_TOKEN_KEEP_SQL(*pSql, token, index); if (TK_TTL == token.type || TK_COMMENT == token.type) { *pSql += index; NEXT_TOKEN_WITH_PREV(*pSql, token); } else { break; } } while (1); return TSDB_CODE_SUCCESS; } // pSql -> stb_name [(tag1_name, ...)] TAGS (tag1_value, ...) static int32_t ignoreUsingClause(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) { const char** pSql = &pStmt->pSql; int32_t code = TSDB_CODE_SUCCESS; SToken token; NEXT_TOKEN(*pSql, token); NEXT_TOKEN(*pSql, token); if (TK_NK_LP == token.type) { code = skipParentheses(pCxt, pSql); if (TSDB_CODE_SUCCESS == code) { NEXT_TOKEN(*pSql, token); } } // pSql -> TAGS (tag1_value, ...) if (TSDB_CODE_SUCCESS == code) { if (TK_TAGS != token.type) { code = buildSyntaxErrMsg(&pCxt->msg, "TAGS is expected", token.z); } else { NEXT_TOKEN(*pSql, token); } } if (TSDB_CODE_SUCCESS == code) { if (TK_NK_LP != token.type) { code = buildSyntaxErrMsg(&pCxt->msg, "( is expected", token.z); } else { code = skipParentheses(pCxt, pSql); } } if (TSDB_CODE_SUCCESS == code) { code = skipTableOptions(pCxt, pSql); } return code; } static int32_t ignoreUsingClauseAndCheckTagValues(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) { const char** pSql = &pStmt->pSql; int32_t code = TSDB_CODE_SUCCESS; code = parseBoundTagsClause(pCxt, pStmt); if (TSDB_CODE_SUCCESS != code) { return code; } // pSql -> TAGS (tag1_value, ...) code = parseTagsClause(pCxt, pStmt, true); if (TSDB_CODE_SUCCESS != code) { return code; } if (TSDB_CODE_SUCCESS == code) { code = skipTableOptions(pCxt, pSql); } return code; } static int32_t parseDuplicateUsingClause(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, bool* pDuplicate) { int32_t code = TSDB_CODE_SUCCESS; *pDuplicate = false; char tbFName[TSDB_TABLE_FNAME_LEN]; code = tNameExtractFullName(&pStmt->targetTableName, tbFName); if (TSDB_CODE_SUCCESS != code) { return code; } STableMeta** pMeta = taosHashGet(pStmt->pSubTableHashObj, tbFName, strlen(tbFName)); if (NULL != pMeta) { *pDuplicate = true; pCxt->missCache = false; code = cloneTableMeta(*pMeta, &pStmt->pTableMeta); if (TSDB_CODE_SUCCESS != code) { return code; } return ignoreUsingClause(pCxt, pStmt); } return code; } typedef enum { BOUND_TAGS, BOUND_COLUMNS, BOUND_ALL_AND_TBNAME } EBoundColumnsType; static int32_t getTbnameSchemaIndex(STableMeta* pTableMeta) { return pTableMeta->tableInfo.numOfTags + pTableMeta->tableInfo.numOfColumns; } // pStmt->pSql -> field1_name, ...) static int32_t parseBoundColumns(SInsertParseContext* pCxt, const char** pSql, EBoundColumnsType boundColsType, STableMeta* pTableMeta, SBoundColInfo* pBoundInfo) { SSchema* pSchema = NULL; if (boundColsType == BOUND_TAGS) { pSchema = getTableTagSchema(pTableMeta); } else if (boundColsType == BOUND_COLUMNS) { pSchema = getTableColumnSchema(pTableMeta); } else { pSchema = pTableMeta->schema; if (pBoundInfo->numOfCols != getTbnameSchemaIndex(pTableMeta) + 1) { return TSDB_CODE_PAR_INTERNAL_ERROR; } } int32_t tbnameSchemaIndex = getTbnameSchemaIndex(pTableMeta); bool* pUseCols = taosMemoryCalloc(pBoundInfo->numOfCols, sizeof(bool)); if (NULL == pUseCols) { return terrno; } pBoundInfo->numOfBound = 0; pBoundInfo->hasBoundCols = true; bool hasPK = pTableMeta->tableInfo.numOfPKs; int16_t numOfBoundPKs = 0; int16_t lastColIdx = -1; // last column found int32_t code = TSDB_CODE_SUCCESS; while (TSDB_CODE_SUCCESS == code) { SToken token; NEXT_TOKEN(*pSql, token); if (TK_NK_RP == token.type) { break; } char tmpTokenBuf[TSDB_COL_NAME_LEN + 2] = {0}; // used for deleting Escape character backstick(`) strncpy(tmpTokenBuf, token.z, token.n); token.z = tmpTokenBuf; token.n = strdequote(token.z); if (boundColsType == BOUND_ALL_AND_TBNAME && token.n == strlen("tbname") && (strcasecmp(token.z, "tbname") == 0)) { pBoundInfo->pColIndex[pBoundInfo->numOfBound] = tbnameSchemaIndex; pUseCols[tbnameSchemaIndex] = true; ++pBoundInfo->numOfBound; continue; } int16_t t = lastColIdx + 1; int16_t end = (boundColsType == BOUND_ALL_AND_TBNAME) ? (pBoundInfo->numOfCols - 1) : pBoundInfo->numOfCols; int16_t index = insFindCol(&token, t, end, pSchema); if (index < 0 && t > 0) { index = insFindCol(&token, 0, t, pSchema); } if (index < 0) { code = generateSyntaxErrMsg(&pCxt->msg, TSDB_CODE_PAR_INVALID_COLUMN, token.z); } else if (pUseCols[index]) { code = buildSyntaxErrMsg(&pCxt->msg, "duplicated column name", token.z); } else { lastColIdx = index; pUseCols[index] = true; pBoundInfo->pColIndex[pBoundInfo->numOfBound] = index; ++pBoundInfo->numOfBound; if (hasPK && (pSchema[index].flags & COL_IS_KEY)) ++numOfBoundPKs; } } if (TSDB_CODE_SUCCESS == code && (BOUND_TAGS != boundColsType)) { if (!pUseCols[0]) { code = buildInvalidOperationMsg(&pCxt->msg, "Primary timestamp column should not be null"); } if (numOfBoundPKs != pTableMeta->tableInfo.numOfPKs) { code = buildInvalidOperationMsg(&pCxt->msg, "Primary key column should not be none"); } } if (TSDB_CODE_SUCCESS == code && (BOUND_ALL_AND_TBNAME == boundColsType) && !pUseCols[tbnameSchemaIndex]) { code = buildInvalidOperationMsg(&pCxt->msg, "tbname column should not be null"); } taosMemoryFree(pUseCols); return code; } static int32_t parseTimestampOrInterval(const char** end, SToken* pToken, int16_t timePrec, int64_t* ts, int64_t* interval, SMsgBuf* pMsgBuf, bool* isTs, timezone_t tz) { if (pToken->type == TK_NOW) { *isTs = true; *ts = taosGetTimestamp(timePrec); } else if (pToken->type == TK_TODAY) { *isTs = true; *ts = taosGetTimestampToday(timePrec, tz); } else if (pToken->type == TK_NK_INTEGER) { *isTs = true; if (TSDB_CODE_SUCCESS != toInteger(pToken->z, pToken->n, 10, ts)) { return buildSyntaxErrMsg(pMsgBuf, "invalid timestamp format", pToken->z); } } else if (pToken->type == TK_NK_VARIABLE) { char unit = 0; *isTs = false; if (parseAbsoluteDuration(pToken->z, pToken->n, interval, &unit, timePrec) != TSDB_CODE_SUCCESS) { return TSDB_CODE_TSC_INVALID_OPERATION; } } else { // parse the RFC-3339/ISO-8601 timestamp format string *isTs = true; if (taosParseTime(pToken->z, ts, pToken->n, timePrec, tz) != TSDB_CODE_SUCCESS) { if ((pToken->n == 0) || (pToken->type != TK_NK_STRING && pToken->type != TK_NK_HEX && pToken->type != TK_NK_BIN)) { return buildSyntaxErrMsg(pMsgBuf, "invalid timestamp format", pToken->z); } if (IS_NOW_STR(pToken->z, pToken->n)) { *isTs = true; *ts = taosGetTimestamp(timePrec); } else if (IS_TODAY_STR(pToken->z, pToken->n)) { *isTs = true; *ts = taosGetTimestampToday(timePrec, tz); } else if (TSDB_CODE_SUCCESS == toIntegerPure(pToken->z, pToken->n, 10, ts)) { *isTs = true; } else { return buildSyntaxErrMsg(pMsgBuf, "invalid timestamp format", pToken->z); } } } return TSDB_CODE_SUCCESS; } static int parseTime(const char** end, SToken* pToken, int16_t timePrec, int64_t* time, SMsgBuf* pMsgBuf, timezone_t tz) { int32_t index = 0, i = 0; int64_t interval = 0, tempInterval = 0; int64_t ts = 0, tempTs = 0; bool firstIsTS = false, secondIsTs = false; const char* pTokenEnd = *end; if (TSDB_CODE_SUCCESS != parseTimestampOrInterval(&pTokenEnd, pToken, timePrec, &ts, &interval, pMsgBuf, &firstIsTS, tz)) { return buildSyntaxErrMsg(pMsgBuf, "invalid timestamp format", pToken->z); } if (firstIsTS) { *time = ts; } for (int k = pToken->n; pToken->z[k] != '\0'; k++) { if (pToken->z[k] == ' ' || pToken->z[k] == '\t') continue; if (pToken->z[k] == '(') { // for insert NOW()/TODAY() if (pToken->z[k + 1] == ')') { *end = pTokenEnd = &pToken->z[k + 2]; ++k; continue; } else { char nc = pToken->z[k + 1]; while (nc == ' ' || nc == '\t' || nc == '\n' || nc == '\r' || nc == '\f') { nc = pToken->z[(++k) + 1]; } if (nc == ')') { *end = pTokenEnd = &pToken->z[k + 2]; ++k; continue; } } } if (pToken->z[k] == ',') { *end = pTokenEnd; if (!firstIsTS) { return buildSyntaxErrMsg(pMsgBuf, "invalid timestamp format", pToken->z); } *time = ts; return TSDB_CODE_SUCCESS; } break; } while (pTokenEnd[i] != '\0') { if (pTokenEnd[i] == ' ' || pTokenEnd[i] == '\t') { i++; continue; } else if (pTokenEnd[i] == ',' || pTokenEnd[i] == ')') { *end = pTokenEnd + i; if (!firstIsTS) { return buildSyntaxErrMsg(pMsgBuf, "invalid timestamp format", pToken->z); } *time = ts; return TSDB_CODE_SUCCESS; } else { break; } } pTokenEnd = pTokenEnd + i; index = 0; SToken token = tStrGetToken(pTokenEnd, &index, false, NULL); if (token.type == TK_NK_MINUS || token.type == TK_NK_PLUS) { pTokenEnd += index; index = 0; SToken valueToken = tStrGetToken(pTokenEnd, &index, false, NULL); pTokenEnd += index; char tmpTokenBuf[TSDB_MAX_BYTES_PER_ROW]; if (TK_NK_STRING == valueToken.type) { if (valueToken.n >= TSDB_MAX_BYTES_PER_ROW) { return buildSyntaxErrMsg(pMsgBuf, "invalid timestamp format", valueToken.z); } int32_t len = trimString(valueToken.z, valueToken.n, tmpTokenBuf, TSDB_MAX_BYTES_PER_ROW); valueToken.z = tmpTokenBuf; valueToken.n = len; } if (TSDB_CODE_SUCCESS != parseTimestampOrInterval(&pTokenEnd, &valueToken, timePrec, &tempTs, &tempInterval, pMsgBuf, &secondIsTs, tz)) { return buildSyntaxErrMsg(pMsgBuf, "invalid timestamp format", pToken->z); } if (valueToken.n < 2) { return buildSyntaxErrMsg(pMsgBuf, "value expected in timestamp", token.z); } if (secondIsTs) { // not support operator between tow timestamp, such as today() + now() if (firstIsTS) { return buildSyntaxErrMsg(pMsgBuf, "invalid timestamp format", pToken->z); } ts = tempTs; } else { // not support operator between tow interval, such as 2h + 3s if (!firstIsTS) { return buildSyntaxErrMsg(pMsgBuf, "invalid timestamp format", pToken->z); } interval = tempInterval; } if (token.type == TK_NK_MINUS) { // not support interval - ts,such as 2h - today() if (secondIsTs) { return buildSyntaxErrMsg(pMsgBuf, "invalid timestamp format", pToken->z); } *time = ts - interval; } else { *time = ts + interval; } for (int k = valueToken.n; valueToken.z[k] != '\0'; k++) { if (valueToken.z[k] == ' ' || valueToken.z[k] == '\t') continue; if (valueToken.z[k] == '(' && valueToken.z[k + 1] == ')') { // for insert NOW()/TODAY() *end = pTokenEnd = &valueToken.z[k + 2]; k++; continue; } if (valueToken.z[k] == ',' || valueToken.z[k] == ')') { *end = pTokenEnd; return TSDB_CODE_SUCCESS; } return buildSyntaxErrMsg(pMsgBuf, "invalid timestamp format", pToken->z); } } *end = pTokenEnd; return TSDB_CODE_SUCCESS; } // need to call geosFreeBuffer(*output) later static int parseGeometry(SToken* pToken, unsigned char** output, size_t* size) { #ifdef USE_GEOS int32_t code = TSDB_CODE_FAILED; //[ToDo] support to parse WKB as well as WKT if (pToken->type == TK_NK_STRING) { code = initCtxGeomFromText(); if (code != TSDB_CODE_SUCCESS) { return code; } code = doGeomFromText(pToken->z, output, size); if (code != TSDB_CODE_SUCCESS) { return code; } } return code; #else TAOS_RETURN(TSDB_CODE_OPS_NOT_SUPPORT); #endif } static int32_t parseVarbinary(SToken* pToken, uint8_t** pData, uint32_t* nData, int32_t bytes) { if (pToken->type != TK_NK_STRING) { return TSDB_CODE_PAR_INVALID_VARBINARY; } if (isHex(pToken->z + 1, pToken->n - 2)) { if (!isValidateHex(pToken->z + 1, pToken->n - 2)) { return TSDB_CODE_PAR_INVALID_VARBINARY; } void* data = NULL; uint32_t size = 0; if (taosHex2Ascii(pToken->z + 1, pToken->n - 2, &data, &size) < 0) { return TSDB_CODE_OUT_OF_MEMORY; } if (size + VARSTR_HEADER_SIZE > bytes) { taosMemoryFree(data); return TSDB_CODE_PAR_VALUE_TOO_LONG; } *pData = data; *nData = size; } else { *pData = taosMemoryCalloc(1, pToken->n); if (!pData) return terrno; int32_t len = trimString(pToken->z, pToken->n, *pData, pToken->n); *nData = len; if (*nData + VARSTR_HEADER_SIZE > bytes) { return TSDB_CODE_PAR_VALUE_TOO_LONG; } } return TSDB_CODE_SUCCESS; } static int32_t parseTagToken(const char** end, SToken* pToken, SSchema* pSchema, int16_t timePrec, STagVal* val, SMsgBuf* pMsgBuf, timezone_t tz, void *charsetCxt) { int64_t iv; uint64_t uv; char* endptr = NULL; int32_t code = TSDB_CODE_SUCCESS; #if 0 if (isNullValue(pSchema->type, pToken)) { if (TSDB_DATA_TYPE_TIMESTAMP == pSchema->type && PRIMARYKEY_TIMESTAMP_COL_ID == pSchema->colId) { return buildSyntaxErrMsg(pMsgBuf, "Primary timestamp column can not be null", pToken->z); } return TSDB_CODE_SUCCESS; } #endif // strcpy(val->colName, pSchema->name); val->cid = pSchema->colId; val->type = pSchema->type; switch (pSchema->type) { case TSDB_DATA_TYPE_BOOL: { if ((pToken->type == TK_NK_BOOL || pToken->type == TK_NK_STRING) && (pToken->n != 0)) { if (IS_TRUE_STR(pToken->z, pToken->n)) { *(int8_t*)(&val->i64) = TRUE_VALUE; } else if (IS_FALSE_STR(pToken->z, pToken->n)) { *(int8_t*)(&val->i64) = FALSE_VALUE; } else if (TSDB_CODE_SUCCESS == toDoubleEx(pToken->z, pToken->n, pToken->type, (double*)&iv)) { *(int8_t*)(&val->i64) = (*(double*)&iv == 0 ? FALSE_VALUE : TRUE_VALUE); } else { return buildSyntaxErrMsg(pMsgBuf, "invalid bool data", pToken->z); } } else if (pToken->type == TK_NK_INTEGER) { *(int8_t*)(&val->i64) = ((taosStr2Int64(pToken->z, NULL, 10) == 0) ? FALSE_VALUE : TRUE_VALUE); } else if (pToken->type == TK_NK_FLOAT) { *(int8_t*)(&val->i64) = ((taosStr2Double(pToken->z, NULL) == 0) ? FALSE_VALUE : TRUE_VALUE); } else if ((pToken->type == TK_NK_HEX || pToken->type == TK_NK_BIN) && (TSDB_CODE_SUCCESS == toDoubleEx(pToken->z, pToken->n, pToken->type, (double*)&iv))) { *(int8_t*)(&val->i64) = (*(double*)&iv == 0 ? FALSE_VALUE : TRUE_VALUE); } else { return buildSyntaxErrMsg(pMsgBuf, "invalid bool data", pToken->z); } break; } case TSDB_DATA_TYPE_TINYINT: { code = toIntegerEx(pToken->z, pToken->n, pToken->type, &iv); if (TSDB_CODE_SUCCESS != code) { return buildSyntaxErrMsg(pMsgBuf, "invalid tinyint data", pToken->z); } else if (!IS_VALID_TINYINT(iv)) { return buildSyntaxErrMsg(pMsgBuf, "tinyint data overflow", pToken->z); } *(int8_t*)(&val->i64) = iv; break; } case TSDB_DATA_TYPE_UTINYINT: { code = toUIntegerEx(pToken->z, pToken->n, pToken->type, &uv); if (TSDB_CODE_SUCCESS != code) { return buildSyntaxErrMsg(pMsgBuf, "invalid unsigned tinyint data", pToken->z); } else if (uv > UINT8_MAX) { return buildSyntaxErrMsg(pMsgBuf, "unsigned tinyint data overflow", pToken->z); } *(uint8_t*)(&val->i64) = uv; break; } case TSDB_DATA_TYPE_SMALLINT: { code = toIntegerEx(pToken->z, pToken->n, pToken->type, &iv); if (TSDB_CODE_SUCCESS != code) { return buildSyntaxErrMsg(pMsgBuf, "invalid smallint data", pToken->z); } else if (!IS_VALID_SMALLINT(iv)) { return buildSyntaxErrMsg(pMsgBuf, "smallint data overflow", pToken->z); } *(int16_t*)(&val->i64) = iv; break; } case TSDB_DATA_TYPE_USMALLINT: { code = toUIntegerEx(pToken->z, pToken->n, pToken->type, &uv); if (TSDB_CODE_SUCCESS != code) { return buildSyntaxErrMsg(pMsgBuf, "invalid unsigned smallint data", pToken->z); } else if (uv > UINT16_MAX) { return buildSyntaxErrMsg(pMsgBuf, "unsigned smallint data overflow", pToken->z); } *(uint16_t*)(&val->i64) = uv; break; } case TSDB_DATA_TYPE_INT: { code = toIntegerEx(pToken->z, pToken->n, pToken->type, &iv); if (TSDB_CODE_SUCCESS != code) { return buildSyntaxErrMsg(pMsgBuf, "invalid int data", pToken->z); } else if (!IS_VALID_INT(iv)) { return buildSyntaxErrMsg(pMsgBuf, "int data overflow", pToken->z); } *(int32_t*)(&val->i64) = iv; break; } case TSDB_DATA_TYPE_UINT: { code = toUIntegerEx(pToken->z, pToken->n, pToken->type, &uv); if (TSDB_CODE_SUCCESS != code) { return buildSyntaxErrMsg(pMsgBuf, "invalid unsigned int data", pToken->z); } else if (uv > UINT32_MAX) { return buildSyntaxErrMsg(pMsgBuf, "unsigned int data overflow", pToken->z); } *(uint32_t*)(&val->i64) = uv; break; } case TSDB_DATA_TYPE_BIGINT: { code = toIntegerEx(pToken->z, pToken->n, pToken->type, &iv); if (TSDB_CODE_SUCCESS != code) { return buildSyntaxErrMsg(pMsgBuf, "invalid bigint data", pToken->z); } val->i64 = iv; break; } case TSDB_DATA_TYPE_UBIGINT: { code = toUIntegerEx(pToken->z, pToken->n, pToken->type, &uv); if (TSDB_CODE_SUCCESS != code) { return buildSyntaxErrMsg(pMsgBuf, "invalid unsigned bigint data", pToken->z); } *(uint64_t*)(&val->i64) = uv; break; } case TSDB_DATA_TYPE_FLOAT: { double dv; code = toDoubleEx(pToken->z, pToken->n, pToken->type, &dv); if (TSDB_CODE_SUCCESS != code) { return buildSyntaxErrMsg(pMsgBuf, "illegal float data", pToken->z); } if (dv > FLT_MAX || dv < -FLT_MAX || isinf(dv) || isnan(dv)) { return buildSyntaxErrMsg(pMsgBuf, "illegal float data", pToken->z); } *(float*)(&val->i64) = dv; break; } case TSDB_DATA_TYPE_DOUBLE: { double dv; code = toDoubleEx(pToken->z, pToken->n, pToken->type, &dv); if (TSDB_CODE_SUCCESS != code) { return buildSyntaxErrMsg(pMsgBuf, "illegal float data", pToken->z); } if (((dv == HUGE_VAL || dv == -HUGE_VAL) && ERRNO == ERANGE) || isinf(dv) || isnan(dv)) { return buildSyntaxErrMsg(pMsgBuf, "illegal double data", pToken->z); } *(double*)(&val->i64) = dv; break; } case TSDB_DATA_TYPE_BINARY: { // Too long values will raise the invalid sql error message if (pToken->n + VARSTR_HEADER_SIZE > pSchema->bytes) { return generateSyntaxErrMsg(pMsgBuf, TSDB_CODE_PAR_VALUE_TOO_LONG, pSchema->name); } val->pData = taosStrdup(pToken->z); if (!val->pData) { return terrno; } val->nData = pToken->n; break; } case TSDB_DATA_TYPE_VARBINARY: { code = parseVarbinary(pToken, &val->pData, &val->nData, pSchema->bytes); if (code != TSDB_CODE_SUCCESS) { return generateSyntaxErrMsg(pMsgBuf, code, pSchema->name); } break; } case TSDB_DATA_TYPE_GEOMETRY: { unsigned char* output = NULL; size_t size = 0; code = parseGeometry(pToken, &output, &size); if (code != TSDB_CODE_SUCCESS) { code = buildSyntaxErrMsg(pMsgBuf, getGeosErrMsg(code), pToken->z); } else if (size + VARSTR_HEADER_SIZE > pSchema->bytes) { // Too long values will raise the invalid sql error message code = generateSyntaxErrMsg(pMsgBuf, TSDB_CODE_PAR_VALUE_TOO_LONG, pSchema->name); } else { val->pData = taosMemoryMalloc(size); if (NULL == val->pData) { code = terrno; } else { memcpy(val->pData, output, size); val->nData = size; } } geosFreeBuffer(output); break; } case TSDB_DATA_TYPE_NCHAR: { int32_t output = 0; int64_t realLen = pToken->n << 2; if (realLen > pSchema->bytes - VARSTR_HEADER_SIZE) realLen = pSchema->bytes - VARSTR_HEADER_SIZE; void* p = taosMemoryMalloc(realLen); if (p == NULL) { return terrno; } if (!taosMbsToUcs4(pToken->z, pToken->n, (TdUcs4*)(p), realLen, &output, charsetCxt)) { if (terrno == TAOS_SYSTEM_ERROR(E2BIG)) { taosMemoryFree(p); return generateSyntaxErrMsg(pMsgBuf, TSDB_CODE_PAR_VALUE_TOO_LONG, pSchema->name); } char buf[512] = {0}; snprintf(buf, tListLen(buf), " taosMbsToUcs4 error:%s %d %d", strerror(terrno), ERRNO, EILSEQ); taosMemoryFree(p); return buildSyntaxErrMsg(pMsgBuf, buf, pToken->z); } val->pData = p; val->nData = output; break; } case TSDB_DATA_TYPE_TIMESTAMP: { if (parseTime(end, pToken, timePrec, &iv, pMsgBuf, tz) != TSDB_CODE_SUCCESS) { return buildSyntaxErrMsg(pMsgBuf, "invalid timestamp", pToken->z); } val->i64 = iv; break; } } return code; } // input pStmt->pSql: [(tag1_name, ...)] TAGS (tag1_value, ...) ... // output pStmt->pSql: TAGS (tag1_value, ...) ... static int32_t parseBoundTagsClause(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) { int32_t code = insInitBoundColsInfo(getNumOfTags(pStmt->pTableMeta), &pCxt->tags); if (TSDB_CODE_SUCCESS != code) { return code; } SToken token; int32_t index = 0; NEXT_TOKEN_KEEP_SQL(pStmt->pSql, token, index); if (TK_NK_LP != token.type) { return TSDB_CODE_SUCCESS; } pStmt->pSql += index; return parseBoundColumns(pCxt, &pStmt->pSql, BOUND_TAGS, pStmt->pTableMeta, &pCxt->tags); } int32_t parseTagValue(SMsgBuf* pMsgBuf, const char** pSql, uint8_t precision, SSchema* pTagSchema, SToken* pToken, SArray* pTagName, SArray* pTagVals, STag** pTag, timezone_t tz, void *charsetCxt) { bool isNull = isNullValue(pTagSchema->type, pToken); if (!isNull && pTagName) { if (NULL == taosArrayPush(pTagName, pTagSchema->name)) { return terrno; } } if (pTagSchema->type == TSDB_DATA_TYPE_JSON) { if (pToken->n > (TSDB_MAX_JSON_TAG_LEN - VARSTR_HEADER_SIZE) / TSDB_NCHAR_SIZE) { return buildSyntaxErrMsg(pMsgBuf, "json string too long than 4095", pToken->z); } if (isNull) { return tTagNew(pTagVals, 1, true, pTag); } else { return parseJsontoTagData(pToken->z, pTagVals, pTag, pMsgBuf, charsetCxt); } } if (isNull) return 0; STagVal val = {0}; int32_t code = parseTagToken(pSql, pToken, pTagSchema, precision, &val, pMsgBuf, tz, charsetCxt); if (TSDB_CODE_SUCCESS == code) { if (NULL == taosArrayPush(pTagVals, &val)) { code = terrno; } } return code; } static int32_t buildCreateTbReq(SVnodeModifyOpStmt* pStmt, STag* pTag, SArray* pTagName) { if (pStmt->pCreateTblReq) { tdDestroySVCreateTbReq(pStmt->pCreateTblReq); taosMemoryFreeClear(pStmt->pCreateTblReq); } pStmt->pCreateTblReq = taosMemoryCalloc(1, sizeof(SVCreateTbReq)); if (NULL == pStmt->pCreateTblReq) { return terrno; } return insBuildCreateTbReq(pStmt->pCreateTblReq, pStmt->targetTableName.tname, pTag, pStmt->pTableMeta->suid, pStmt->usingTableName.tname, pTagName, pStmt->pTableMeta->tableInfo.numOfTags, TSDB_DEFAULT_TABLE_TTL); } int32_t checkAndTrimValue(SToken* pToken, char* tmpTokenBuf, SMsgBuf* pMsgBuf, int8_t type) { if (pToken->type == TK_NK_QUESTION) { return buildInvalidOperationMsg(pMsgBuf, "insert into super table syntax is not supported for stmt"); } if ((pToken->type != TK_NOW && pToken->type != TK_TODAY && pToken->type != TK_NK_INTEGER && pToken->type != TK_NK_STRING && pToken->type != TK_NK_FLOAT && pToken->type != TK_NK_BOOL && pToken->type != TK_NULL && pToken->type != TK_NK_HEX && pToken->type != TK_NK_OCT && pToken->type != TK_NK_BIN && pToken->type != TK_NK_VARIABLE) || (pToken->n == 0) || (pToken->type == TK_NK_RP)) { return buildSyntaxErrMsg(pMsgBuf, "invalid data or symbol", pToken->z); } // Remove quotation marks if (TK_NK_STRING == pToken->type && type != TSDB_DATA_TYPE_VARBINARY) { if (pToken->n >= TSDB_MAX_BYTES_PER_ROW) { return buildSyntaxErrMsg(pMsgBuf, "too long string", pToken->z); } int32_t len = trimString(pToken->z, pToken->n, tmpTokenBuf, TSDB_MAX_BYTES_PER_ROW); pToken->z = tmpTokenBuf; pToken->n = len; } return TSDB_CODE_SUCCESS; } typedef struct SRewriteTagCondCxt { SArray* pTagVals; SArray* pTagName; int32_t code; } SRewriteTagCondCxt; static int32_t rewriteTagCondColumnImpl(STagVal* pVal, SNode** pNode) { SValueNode* pValue = NULL; int32_t code = nodesMakeNode(QUERY_NODE_VALUE, (SNode**)&pValue); if (NULL == pValue) { return code; } pValue->node.resType = ((SColumnNode*)*pNode)->node.resType; nodesDestroyNode(*pNode); *pNode = (SNode*)pValue; switch (pVal->type) { case TSDB_DATA_TYPE_BOOL: pValue->datum.b = *(int8_t*)(&pVal->i64); *(bool*)&pValue->typeData = pValue->datum.b; break; case TSDB_DATA_TYPE_TINYINT: pValue->datum.i = *(int8_t*)(&pVal->i64); *(int8_t*)&pValue->typeData = pValue->datum.i; break; case TSDB_DATA_TYPE_SMALLINT: pValue->datum.i = *(int16_t*)(&pVal->i64); *(int16_t*)&pValue->typeData = pValue->datum.i; break; case TSDB_DATA_TYPE_INT: pValue->datum.i = *(int32_t*)(&pVal->i64); *(int32_t*)&pValue->typeData = pValue->datum.i; break; case TSDB_DATA_TYPE_BIGINT: pValue->datum.i = pVal->i64; pValue->typeData = pValue->datum.i; break; case TSDB_DATA_TYPE_FLOAT: pValue->datum.d = *(float*)(&pVal->i64); *(float*)&pValue->typeData = pValue->datum.d; break; case TSDB_DATA_TYPE_DOUBLE: pValue->datum.d = *(double*)(&pVal->i64); *(double*)&pValue->typeData = pValue->datum.d; break; case TSDB_DATA_TYPE_VARCHAR: case TSDB_DATA_TYPE_VARBINARY: case TSDB_DATA_TYPE_NCHAR: pValue->datum.p = taosMemoryCalloc(1, pVal->nData + VARSTR_HEADER_SIZE); if (NULL == pValue->datum.p) { return terrno; } varDataSetLen(pValue->datum.p, pVal->nData); memcpy(varDataVal(pValue->datum.p), pVal->pData, pVal->nData); break; case TSDB_DATA_TYPE_TIMESTAMP: pValue->datum.i = pVal->i64; pValue->typeData = pValue->datum.i; break; case TSDB_DATA_TYPE_UTINYINT: pValue->datum.i = *(uint8_t*)(&pVal->i64); *(uint8_t*)&pValue->typeData = pValue->datum.i; break; case TSDB_DATA_TYPE_USMALLINT: pValue->datum.i = *(uint16_t*)(&pVal->i64); *(uint16_t*)&pValue->typeData = pValue->datum.i; break; case TSDB_DATA_TYPE_UINT: pValue->datum.i = *(uint32_t*)(&pVal->i64); *(uint32_t*)&pValue->typeData = pValue->datum.i; break; case TSDB_DATA_TYPE_UBIGINT: pValue->datum.i = *(uint64_t*)(&pVal->i64); *(uint64_t*)&pValue->typeData = pValue->datum.i; break; case TSDB_DATA_TYPE_JSON: case TSDB_DATA_TYPE_DECIMAL: case TSDB_DATA_TYPE_BLOB: case TSDB_DATA_TYPE_MEDIUMBLOB: default: return TSDB_CODE_FAILED; } return TSDB_CODE_SUCCESS; } static int32_t rewriteTagCondColumn(SArray* pTagVals, SArray* pTagName, SNode** pNode) { SColumnNode* pCol = (SColumnNode*)*pNode; int32_t ntags = taosArrayGetSize(pTagName); for (int32_t i = 0; i < ntags; ++i) { char* pTagColName = taosArrayGet(pTagName, i); if (0 == strcmp(pTagColName, pCol->colName)) { return rewriteTagCondColumnImpl(taosArrayGet(pTagVals, i), pNode); } } return TSDB_CODE_PAR_PERMISSION_DENIED; } static EDealRes rewriteTagCond(SNode** pNode, void* pContext) { if (QUERY_NODE_COLUMN == nodeType(*pNode)) { SRewriteTagCondCxt* pCxt = pContext; pCxt->code = rewriteTagCondColumn(pCxt->pTagVals, pCxt->pTagName, pNode); return (TSDB_CODE_SUCCESS == pCxt->code ? DEAL_RES_IGNORE_CHILD : DEAL_RES_ERROR); } return DEAL_RES_CONTINUE; } static int32_t setTagVal(SArray* pTagVals, SArray* pTagName, SNode* pCond) { SRewriteTagCondCxt cxt = {.code = TSDB_CODE_SUCCESS, .pTagVals = pTagVals, .pTagName = pTagName}; nodesRewriteExpr(&pCond, rewriteTagCond, &cxt); return cxt.code; } static int32_t checkTagCondResult(SNode* pResult) { return (QUERY_NODE_VALUE == nodeType(pResult) && ((SValueNode*)pResult)->datum.b) ? TSDB_CODE_SUCCESS : TSDB_CODE_PAR_PERMISSION_DENIED; } static int32_t checkSubtablePrivilege(SArray* pTagVals, SArray* pTagName, SNode** pCond) { int32_t code = setTagVal(pTagVals, pTagName, *pCond); if (TSDB_CODE_SUCCESS == code) { code = scalarCalculateConstants(*pCond, pCond); } if (TSDB_CODE_SUCCESS == code) { code = checkTagCondResult(*pCond); } NODES_DESTORY_NODE(*pCond); return code; } // pSql -> tag1_value, ...) static int32_t parseTagsClauseImpl(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, bool autoCreate) { int32_t code = TSDB_CODE_SUCCESS; SSchema* pSchema = getTableTagSchema(pStmt->pTableMeta); SArray* pTagVals = NULL; SArray* pTagName = NULL; uint8_t precision = pStmt->pTableMeta->tableInfo.precision; SToken token; bool isParseBindParam = false; bool isJson = false; STag* pTag = NULL; if (!(pTagVals = taosArrayInit(pCxt->tags.numOfBound, sizeof(STagVal))) || !(pTagName = taosArrayInit(pCxt->tags.numOfBound, TSDB_COL_NAME_LEN))) { code = terrno; goto _exit; } for (int i = 0; TSDB_CODE_SUCCESS == code && i < pCxt->tags.numOfBound; ++i) { NEXT_TOKEN_WITH_PREV(pStmt->pSql, token); if (token.type == TK_NK_QUESTION) { isParseBindParam = true; if (NULL == pCxt->pComCxt->pStmtCb) { code = buildSyntaxErrMsg(&pCxt->msg, "? only used in stmt", token.z); break; } if (pTagVals->size != 0) { code = buildSyntaxErrMsg(&pCxt->msg, "no mix usage for ? and tag values", token.z); break; } continue; } if (isParseBindParam) { code = buildInvalidOperationMsg(&pCxt->msg, "no mix usage for ? and tag values"); break; } SSchema* pTagSchema = &pSchema[pCxt->tags.pColIndex[i]]; isJson = pTagSchema->type == TSDB_DATA_TYPE_JSON; code = checkAndTrimValue(&token, pCxt->tmpTokenBuf, &pCxt->msg, pTagSchema->type); if (TSDB_CODE_SUCCESS == code && TK_NK_VARIABLE == token.type) { code = buildSyntaxErrMsg(&pCxt->msg, "not expected tags values ", token.z); } if (TSDB_CODE_SUCCESS == code) { code = parseTagValue(&pCxt->msg, &pStmt->pSql, precision, pTagSchema, &token, pTagName, pTagVals, &pTag, pCxt->pComCxt->timezone, pCxt->pComCxt->charsetCxt); } } if (TSDB_CODE_SUCCESS == code && NULL != pStmt->pTagCond) { code = checkSubtablePrivilege(pTagVals, pTagName, &pStmt->pTagCond); } if (TSDB_CODE_SUCCESS == code && !isParseBindParam && !isJson) { code = tTagNew(pTagVals, 1, false, &pTag); } if (TSDB_CODE_SUCCESS == code && !isParseBindParam && !autoCreate) { code = buildCreateTbReq(pStmt, pTag, pTagName); pTag = NULL; } if (code == TSDB_CODE_SUCCESS && !isParseBindParam) { pCxt->stmtTbNameFlag |= IS_FIXED_TAG; } _exit: for (int32_t i = 0; i < taosArrayGetSize(pTagVals); ++i) { STagVal* p = (STagVal*)TARRAY_GET_ELEM(pTagVals, i); if (IS_VAR_DATA_TYPE(p->type)) { taosMemoryFreeClear(p->pData); } } taosArrayDestroy(pTagVals); taosArrayDestroy(pTagName); tTagFree(pTag); return code; } // input pStmt->pSql: TAGS (tag1_value, ...) [table_options] ... // output pStmt->pSql: [table_options] ... static int32_t parseTagsClause(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, bool autoCreate) { SToken token; NEXT_TOKEN(pStmt->pSql, token); if (TK_TAGS != token.type) { return buildSyntaxErrMsg(&pCxt->msg, "TAGS is expected", token.z); } NEXT_TOKEN(pStmt->pSql, token); if (TK_NK_LP != token.type) { return buildSyntaxErrMsg(&pCxt->msg, "( is expected", token.z); } int32_t code = parseTagsClauseImpl(pCxt, pStmt, autoCreate); if (TSDB_CODE_SUCCESS == code) { NEXT_VALID_TOKEN(pStmt->pSql, token); if (TK_NK_COMMA == token.type) { code = generateSyntaxErrMsg(&pCxt->msg, TSDB_CODE_PAR_TAGS_NOT_MATCHED); } else if (TK_NK_RP != token.type) { code = buildSyntaxErrMsg(&pCxt->msg, ") is expected", token.z); } } return code; } static int32_t storeChildTableMeta(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) { pStmt->pTableMeta->suid = pStmt->pTableMeta->uid; pStmt->pTableMeta->uid = pStmt->totalTbNum; pStmt->pTableMeta->tableType = TSDB_CHILD_TABLE; STableMeta* pBackup = NULL; if (TSDB_CODE_SUCCESS != cloneTableMeta(pStmt->pTableMeta, &pBackup)) { return TSDB_CODE_OUT_OF_MEMORY; } char tbFName[TSDB_TABLE_FNAME_LEN]; int32_t code = tNameExtractFullName(&pStmt->targetTableName, tbFName); if (TSDB_CODE_SUCCESS != code) { taosMemoryFree(pBackup); return code; } code = taosHashPut(pStmt->pSubTableHashObj, tbFName, strlen(tbFName), &pBackup, POINTER_BYTES); if (TSDB_CODE_SUCCESS != code) { taosMemoryFree(pBackup); } return code; } static int32_t parseTableOptions(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) { do { int32_t index = 0; SToken token; NEXT_TOKEN_KEEP_SQL(pStmt->pSql, token, index); if (TK_TTL == token.type) { pStmt->pSql += index; NEXT_TOKEN_WITH_PREV(pStmt->pSql, token); if (TK_NK_INTEGER != token.type) { return buildSyntaxErrMsg(&pCxt->msg, "Invalid option ttl", token.z); } pStmt->pCreateTblReq->ttl = taosStr2Int32(token.z, NULL, 10); if (pStmt->pCreateTblReq->ttl < 0) { return buildSyntaxErrMsg(&pCxt->msg, "Invalid option ttl", token.z); } } else if (TK_COMMENT == token.type) { pStmt->pSql += index; NEXT_TOKEN(pStmt->pSql, token); if (TK_NK_STRING != token.type) { return buildSyntaxErrMsg(&pCxt->msg, "Invalid option comment", token.z); } if (token.n >= TSDB_TB_COMMENT_LEN) { return buildSyntaxErrMsg(&pCxt->msg, "comment too long", token.z); } int32_t len = trimString(token.z, token.n, pCxt->tmpTokenBuf, TSDB_TB_COMMENT_LEN); pStmt->pCreateTblReq->comment = taosStrndup(pCxt->tmpTokenBuf, len); if (NULL == pStmt->pCreateTblReq->comment) { return terrno; } pStmt->pCreateTblReq->commentLen = len; } else { break; } } while (1); return TSDB_CODE_SUCCESS; } // input pStmt->pSql: // 1. [(tag1_name, ...)] ... // 2. VALUES ... | FILE ... // output pStmt->pSql: // 1. [(field1_name, ...)] // 2. VALUES ... | FILE ... static int32_t parseUsingClauseBottom(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) { if (!pStmt->usingTableProcessing || pCxt->usingDuplicateTable) { return TSDB_CODE_SUCCESS; } int32_t code = parseBoundTagsClause(pCxt, pStmt); if (TSDB_CODE_SUCCESS == code) { code = parseTagsClause(pCxt, pStmt, false); } if (TSDB_CODE_SUCCESS == code) { code = parseTableOptions(pCxt, pStmt); } return code; } static void setUserAuthInfo(SParseContext* pCxt, SName* pTbName, SUserAuthInfo* pInfo) { snprintf(pInfo->user, sizeof(pInfo->user), "%s", pCxt->pUser); memcpy(&pInfo->tbName, pTbName, sizeof(SName)); pInfo->type = AUTH_TYPE_WRITE; } static int32_t checkAuth(SParseContext* pCxt, SName* pTbName, bool* pMissCache, SNode** pTagCond) { int32_t code = TSDB_CODE_SUCCESS; SUserAuthInfo authInfo = {0}; setUserAuthInfo(pCxt, pTbName, &authInfo); SUserAuthRes authRes = {0}; bool exists = true; if (pCxt->async) { code = catalogChkAuthFromCache(pCxt->pCatalog, &authInfo, &authRes, &exists); } else { SRequestConnInfo conn = {.pTrans = pCxt->pTransporter, .requestId = pCxt->requestId, .requestObjRefId = pCxt->requestRid, .mgmtEps = pCxt->mgmtEpSet}; code = catalogChkAuth(pCxt->pCatalog, &conn, &authInfo, &authRes); } if (TSDB_CODE_SUCCESS == code) { if (!exists) { *pMissCache = true; } else if (!authRes.pass[AUTH_RES_BASIC]) { code = TSDB_CODE_PAR_PERMISSION_DENIED; } else if (NULL != authRes.pCond[AUTH_RES_BASIC]) { *pTagCond = authRes.pCond[AUTH_RES_BASIC]; } } return code; } static int32_t getTableMeta(SInsertParseContext* pCxt, SName* pTbName, STableMeta** pTableMeta, bool* pMissCache, bool bUsingTable) { SParseContext* pComCxt = pCxt->pComCxt; int32_t code = TSDB_CODE_SUCCESS; if (pComCxt->async) { if (bUsingTable) { code = catalogGetCachedSTableMeta(pComCxt->pCatalog, pTbName, pTableMeta); } else { code = catalogGetCachedTableMeta(pComCxt->pCatalog, pTbName, pTableMeta); } } else { SRequestConnInfo conn = {.pTrans = pComCxt->pTransporter, .requestId = pComCxt->requestId, .requestObjRefId = pComCxt->requestRid, .mgmtEps = pComCxt->mgmtEpSet}; if (bUsingTable) { code = catalogGetSTableMeta(pComCxt->pCatalog, &conn, pTbName, pTableMeta); } else { code = catalogGetTableMeta(pComCxt->pCatalog, &conn, pTbName, pTableMeta); } } if (TSDB_CODE_SUCCESS == code) { if (NULL == *pTableMeta) { *pMissCache = true; } else if (bUsingTable && TSDB_SUPER_TABLE != (*pTableMeta)->tableType) { code = buildInvalidOperationMsg(&pCxt->msg, "create table only from super table is allowed"); } else if (((*pTableMeta)->virtualStb) || TSDB_VIRTUAL_CHILD_TABLE == (*pTableMeta)->tableType || TSDB_VIRTUAL_NORMAL_TABLE == (*pTableMeta)->tableType) { code = TSDB_CODE_VTABLE_NOT_SUPPORT_STMT; } } return code; } static int32_t getTargetTableVgroup(SParseContext* pCxt, SVnodeModifyOpStmt* pStmt, bool isStb, bool* pMissCache) { int32_t code = TSDB_CODE_SUCCESS; SVgroupInfo vg; bool exists = true; if (pCxt->async) { code = catalogGetCachedTableHashVgroup(pCxt->pCatalog, &pStmt->targetTableName, &vg, &exists); } else { SRequestConnInfo conn = {.pTrans = pCxt->pTransporter, .requestId = pCxt->requestId, .requestObjRefId = pCxt->requestRid, .mgmtEps = pCxt->mgmtEpSet}; code = catalogGetTableHashVgroup(pCxt->pCatalog, &conn, &pStmt->targetTableName, &vg); } if (TSDB_CODE_SUCCESS == code) { if (exists) { if (isStb) { pStmt->pTableMeta->vgId = vg.vgId; } code = taosHashPut(pStmt->pVgroupsHashObj, (const char*)&vg.vgId, sizeof(vg.vgId), (char*)&vg, sizeof(vg)); } *pMissCache = !exists; } return code; } static int32_t getTargetTableMetaAndVgroup(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, bool* pMissCache) { SParseContext* pComCxt = pCxt->pComCxt; int32_t code = TSDB_CODE_SUCCESS; if (pComCxt->async) { { SVgroupInfo vg; code = catalogGetCachedTableVgMeta(pComCxt->pCatalog, &pStmt->targetTableName, &vg, &pStmt->pTableMeta); if (TSDB_CODE_SUCCESS == code) { if (NULL != pStmt->pTableMeta) { if (pStmt->pTableMeta->tableType == TSDB_SUPER_TABLE) { pStmt->stbSyntax = true; } else { code = taosHashPut(pStmt->pVgroupsHashObj, (const char*)&vg.vgId, sizeof(vg.vgId), (char*)&vg, sizeof(vg)); } } *pMissCache = (NULL == pStmt->pTableMeta); } } } else { bool bUsingTable = false; code = getTableMeta(pCxt, &pStmt->targetTableName, &pStmt->pTableMeta, pMissCache, bUsingTable); if (TSDB_CODE_SUCCESS == code && !pCxt->missCache) { if (TSDB_SUPER_TABLE == pStmt->pTableMeta->tableType) { pStmt->stbSyntax = true; } if (!pStmt->stbSyntax) { code = getTargetTableVgroup(pCxt->pComCxt, pStmt, false, &pCxt->missCache); } } } return code; } static int32_t collectUseTable(const SName* pName, SHashObj* pTable) { char fullName[TSDB_TABLE_FNAME_LEN]; int32_t code = tNameExtractFullName(pName, fullName); if (TSDB_CODE_SUCCESS != code) { return code; } return taosHashPut(pTable, fullName, strlen(fullName), pName, sizeof(SName)); } static int32_t collectUseDatabase(const SName* pName, SHashObj* pDbs) { char dbFName[TSDB_DB_FNAME_LEN] = {0}; (void)tNameGetFullDbName(pName, dbFName); return taosHashPut(pDbs, dbFName, strlen(dbFName), dbFName, sizeof(dbFName)); } static int32_t getTargetTableSchema(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) { if (pCxt->forceUpdate) { pCxt->missCache = true; return TSDB_CODE_SUCCESS; } SNode* pTagCond = NULL; int32_t code = checkAuth(pCxt->pComCxt, &pStmt->targetTableName, &pCxt->missCache, &pTagCond); if (TSDB_CODE_SUCCESS == code && !pCxt->missCache) { code = getTargetTableMetaAndVgroup(pCxt, pStmt, &pCxt->missCache); } if (TSDB_CODE_SUCCESS == code && !pCxt->missCache) { if (TSDB_SUPER_TABLE != pStmt->pTableMeta->tableType) { pCxt->needTableTagVal = (NULL != pTagCond); pCxt->missCache = (NULL != pTagCond); } else { pStmt->pTagCond = NULL; code = nodesCloneNode(pTagCond, &pStmt->pTagCond); } } nodesDestroyNode(pTagCond); if (TSDB_CODE_SUCCESS == code && !pCxt->pComCxt->async) { code = collectUseDatabase(&pStmt->targetTableName, pStmt->pDbFNameHashObj); if (TSDB_CODE_SUCCESS == code) { code = collectUseTable(&pStmt->targetTableName, pStmt->pTableNameHashObj); } } return code; } static int32_t preParseUsingTableName(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, SToken* pTbName) { return insCreateSName(&pStmt->usingTableName, pTbName, pCxt->pComCxt->acctId, pCxt->pComCxt->db, &pCxt->msg); } static int32_t getUsingTableSchema(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, bool* ctbCacheHit) { int32_t code = TSDB_CODE_SUCCESS; STableMeta* pStableMeta = NULL; STableMeta* pCtableMeta = NULL; if (pCxt->forceUpdate) { pCxt->missCache = true; return TSDB_CODE_SUCCESS; } char stableFName[TSDB_TABLE_FNAME_LEN]; code = tNameExtractFullName(&pStmt->usingTableName, stableFName); if (TSDB_CODE_SUCCESS != code) { return code; } char ctableFName[TSDB_TABLE_FNAME_LEN]; code = tNameExtractFullName(&pStmt->targetTableName, ctableFName); if (TSDB_CODE_SUCCESS != code) { return code; } if (strcmp(stableFName, ctableFName) == 0) { return TSDB_CODE_TDB_TABLE_IN_OTHER_STABLE; } if (!pCxt->missCache) { char tbFName[TSDB_TABLE_FNAME_LEN]; code = tNameExtractFullName(&pStmt->usingTableName, tbFName); if (TSDB_CODE_SUCCESS != code) { return code; } STableMeta** ppStableMeta = taosHashGet(pStmt->pSuperTableHashObj, tbFName, strlen(tbFName)); if (NULL != ppStableMeta) { pStableMeta = *ppStableMeta; } if (NULL == pStableMeta) { bool bUsingTable = true; code = getTableMeta(pCxt, &pStmt->usingTableName, &pStableMeta, &pCxt->missCache, bUsingTable); if (TSDB_CODE_SUCCESS == code) { code = taosHashPut(pStmt->pSuperTableHashObj, tbFName, strlen(tbFName), &pStableMeta, POINTER_BYTES); } else { taosMemoryFreeClear(pStableMeta); } } } if (pCxt->isStmtBind) { goto _no_ctb_cache; } if (TSDB_CODE_SUCCESS == code && !pCxt->missCache) { bool bUsingTable = false; code = getTableMeta(pCxt, &pStmt->targetTableName, &pCtableMeta, &pCxt->missCache, bUsingTable); } if (TSDB_CODE_SUCCESS == code && !pCxt->missCache) { code = (pStableMeta->suid == pCtableMeta->suid) ? TSDB_CODE_SUCCESS : TSDB_CODE_TDB_TABLE_IN_OTHER_STABLE; *ctbCacheHit = true; } _no_ctb_cache: if (TSDB_CODE_SUCCESS == code) { if (*ctbCacheHit) { code = cloneTableMeta(pCtableMeta, &pStmt->pTableMeta); } else { code = cloneTableMeta(pStableMeta, &pStmt->pTableMeta); } } taosMemoryFree(pCtableMeta); if (TSDB_CODE_SUCCESS == code && !pCxt->missCache) { code = getTargetTableVgroup(pCxt->pComCxt, pStmt, true, &pCxt->missCache); } if (TSDB_CODE_SUCCESS == code && !pCxt->pComCxt->async) { code = collectUseDatabase(&pStmt->usingTableName, pStmt->pDbFNameHashObj); if (TSDB_CODE_SUCCESS == code) { code = collectUseTable(&pStmt->usingTableName, pStmt->pTableNameHashObj); } } return code; } static int32_t parseUsingTableNameImpl(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) { SToken token; NEXT_TOKEN(pStmt->pSql, token); bool ctbCacheHit = false; int32_t code = preParseUsingTableName(pCxt, pStmt, &token); if (TSDB_CODE_SUCCESS == code) { code = getUsingTableSchema(pCxt, pStmt, &ctbCacheHit); if (TSDB_CODE_SUCCESS == code && ctbCacheHit && !pCxt->missCache) { pStmt->usingTableProcessing = false; return ignoreUsingClauseAndCheckTagValues(pCxt, pStmt); } } if (TSDB_CODE_SUCCESS == code && !pCxt->missCache) { code = storeChildTableMeta(pCxt, pStmt); } return code; } // input pStmt->pSql: // 1(care). [USING stb_name [(tag1_name, ...)] TAGS (tag1_value, ...) [table_options]] ... // 2. VALUES ... | FILE ... // output pStmt->pSql: // 1. [(tag1_name, ...)] TAGS (tag1_value, ...) [table_options]] ... // 2. VALUES ... | FILE ... static int32_t parseUsingTableName(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) { SToken token; int32_t index = 0; NEXT_TOKEN_KEEP_SQL(pStmt->pSql, token, index); if (TK_USING != token.type) { return getTargetTableSchema(pCxt, pStmt); } pStmt->usingTableProcessing = true; pCxt->stmtTbNameFlag |= USING_CLAUSE; // pStmt->pSql -> stb_name [(tag1_name, ...) pStmt->pSql += index; int32_t code = parseDuplicateUsingClause(pCxt, pStmt, &pCxt->usingDuplicateTable); if (TSDB_CODE_SUCCESS == code && !pCxt->usingDuplicateTable) { return parseUsingTableNameImpl(pCxt, pStmt); } return code; } static int32_t preParseTargetTableName(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, SToken* pTbName) { int32_t code = insCreateSName(&pStmt->targetTableName, pTbName, pCxt->pComCxt->acctId, pCxt->pComCxt->db, &pCxt->msg); if (TSDB_CODE_SUCCESS == code) { if (IS_SYS_DBNAME(pStmt->targetTableName.dbname)) { return TSDB_CODE_PAR_SYSTABLE_NOT_ALLOWED; } } return code; } // input pStmt->pSql: // 1(care). [(field1_name, ...)] ... // 2. [ USING ... ] ... // 3. VALUES ... | FILE ... // output pStmt->pSql: // 1. [ USING ... ] ... // 2. VALUES ... | FILE ... static int32_t preParseBoundColumnsClause(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) { SToken token; int32_t index = 0; NEXT_TOKEN_KEEP_SQL(pStmt->pSql, token, index); if (TK_NK_LP != token.type) { return TSDB_CODE_SUCCESS; } // pStmt->pSql -> field1_name, ...) pStmt->pSql += index; pStmt->pBoundCols = pStmt->pSql; return skipParentheses(pCxt, &pStmt->pSql); } static int32_t getTableDataCxt(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, STableDataCxt** pTableCxt) { if (pCxt->pComCxt->async) { return insGetTableDataCxt(pStmt->pTableBlockHashObj, &pStmt->pTableMeta->uid, sizeof(pStmt->pTableMeta->uid), pStmt->pTableMeta, &pStmt->pCreateTblReq, pTableCxt, false, false); } char tbFName[TSDB_TABLE_FNAME_LEN]; int32_t code = 0; if ((pCxt->stmtTbNameFlag & NO_DATA_USING_CLAUSE) == USING_CLAUSE) { tstrncpy(pStmt->targetTableName.tname, pStmt->usingTableName.tname, sizeof(pStmt->targetTableName.tname)); tstrncpy(pStmt->targetTableName.dbname, pStmt->usingTableName.dbname, sizeof(pStmt->targetTableName.dbname)); pStmt->targetTableName.type = TSDB_SUPER_TABLE; pStmt->pTableMeta->tableType = TSDB_SUPER_TABLE; } code = tNameExtractFullName(&pStmt->targetTableName, tbFName); if (TSDB_CODE_SUCCESS != code) { return code; } if (pStmt->usingTableProcessing) { pStmt->pTableMeta->uid = 0; } return insGetTableDataCxt(pStmt->pTableBlockHashObj, tbFName, strlen(tbFName), pStmt->pTableMeta, &pStmt->pCreateTblReq, pTableCxt, NULL != pCxt->pComCxt->pStmtCb, false); } static int32_t parseBoundColumnsClause(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, STableDataCxt* pTableCxt) { SToken token; int32_t index = 0; NEXT_TOKEN_KEEP_SQL(pStmt->pSql, token, index); if (TK_NK_LP == token.type) { pStmt->pSql += index; if (NULL != pStmt->pBoundCols) { return buildSyntaxErrMsg(&pCxt->msg, "keyword VALUES or FILE is expected", token.z); } // pStmt->pSql -> field1_name, ...) return parseBoundColumns(pCxt, &pStmt->pSql, BOUND_COLUMNS, pStmt->pTableMeta, &pTableCxt->boundColsInfo); } if (NULL != pStmt->pBoundCols) { return parseBoundColumns(pCxt, &pStmt->pBoundCols, BOUND_COLUMNS, pStmt->pTableMeta, &pTableCxt->boundColsInfo); } else if (pTableCxt->boundColsInfo.hasBoundCols) { insResetBoundColsInfo(&pTableCxt->boundColsInfo); } return TSDB_CODE_SUCCESS; } int32_t initTableColSubmitData(STableDataCxt* pTableCxt) { if (0 == (pTableCxt->pData->flags & SUBMIT_REQ_COLUMN_DATA_FORMAT)) { return TSDB_CODE_SUCCESS; } for (int32_t i = 0; i < pTableCxt->boundColsInfo.numOfBound; ++i) { SSchema* pSchema = &pTableCxt->pMeta->schema[pTableCxt->boundColsInfo.pColIndex[i]]; SColData* pCol = taosArrayReserve(pTableCxt->pData->aCol, 1); if (NULL == pCol) { return terrno; } tColDataInit(pCol, pSchema->colId, pSchema->type, pSchema->flags); } return TSDB_CODE_SUCCESS; } int32_t initTableColSubmitDataWithBoundInfo(STableDataCxt* pTableCxt, SBoundColInfo pBoundColsInfo) { insDestroyBoundColInfo(&(pTableCxt->boundColsInfo)); pTableCxt->boundColsInfo = pBoundColsInfo; for (int32_t i = 0; i < pBoundColsInfo.numOfBound; ++i) { SSchema* pSchema = &pTableCxt->pMeta->schema[pTableCxt->boundColsInfo.pColIndex[i]]; SColData* pCol = taosArrayReserve(pTableCxt->pData->aCol, 1); if (NULL == pCol) { return terrno; } tColDataInit(pCol, pSchema->colId, pSchema->type, pSchema->flags); } return TSDB_CODE_SUCCESS; } // input pStmt->pSql: // 1. [(tag1_name, ...)] ... // 2. VALUES ... | FILE ... // output pStmt->pSql: VALUES ... | FILE ... static int32_t parseSchemaClauseBottom(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, STableDataCxt** pTableCxt) { int32_t code = parseUsingClauseBottom(pCxt, pStmt); if (TSDB_CODE_SUCCESS == code) { code = getTableDataCxt(pCxt, pStmt, pTableCxt); } if (TSDB_CODE_SUCCESS == code) { code = parseBoundColumnsClause(pCxt, pStmt, *pTableCxt); } if (TSDB_CODE_SUCCESS == code) { code = initTableColSubmitData(*pTableCxt); } return code; } // input pStmt->pSql: [(field1_name, ...)] [ USING ... ] VALUES ... | FILE ... // output pStmt->pSql: // 1. [(tag1_name, ...)] ... // 2. VALUES ... | FILE ... static int32_t parseSchemaClauseTop(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, SToken* pTbName) { int32_t code = preParseTargetTableName(pCxt, pStmt, pTbName); if (TSDB_CODE_SUCCESS == code) { // option: [(field1_name, ...)] code = preParseBoundColumnsClause(pCxt, pStmt); } if (TSDB_CODE_SUCCESS == code) { // option: [USING stb_name] code = parseUsingTableName(pCxt, pStmt); } return code; } static int32_t parseValueTokenImpl(SInsertParseContext* pCxt, const char** pSql, SToken* pToken, SSchema* pSchema, const SSchemaExt* pExtSchema, int16_t timePrec, SColVal* pVal) { switch (pSchema->type) { case TSDB_DATA_TYPE_BOOL: { if ((pToken->type == TK_NK_BOOL || pToken->type == TK_NK_STRING) && (pToken->n != 0)) { if (IS_TRUE_STR(pToken->z, pToken->n)) { VALUE_SET_TRIVIAL_DATUM(&pVal->value, TRUE_VALUE); } else if (IS_FALSE_STR(pToken->z, pToken->n)) { VALUE_SET_TRIVIAL_DATUM(&pVal->value, FALSE_VALUE); } else if (TSDB_CODE_SUCCESS == toDoubleEx(pToken->z, pToken->n, pToken->type, (double*)&VALUE_GET_TRIVIAL_DATUM(&pVal->value))) { int8_t v = (*(double*)&VALUE_GET_TRIVIAL_DATUM(&pVal->value) == 0 ? FALSE_VALUE : TRUE_VALUE); valueSetDatum(&pVal->value, TSDB_DATA_TYPE_BOOL, &v, tDataTypes[TSDB_DATA_TYPE_BOOL].bytes); } else { return buildSyntaxErrMsg(&pCxt->msg, "invalid bool data", pToken->z); } } else if (pToken->type == TK_NK_INTEGER) { int8_t v = ((taosStr2Int64(pToken->z, NULL, 10) == 0) ? FALSE_VALUE : TRUE_VALUE); VALUE_SET_TRIVIAL_DATUM(&pVal->value, v); } else if (pToken->type == TK_NK_FLOAT) { int8_t v = ((taosStr2Double(pToken->z, NULL) == 0) ? FALSE_VALUE : TRUE_VALUE); VALUE_SET_TRIVIAL_DATUM(&pVal->value, v); } else if ((pToken->type == TK_NK_HEX || pToken->type == TK_NK_BIN) && (TSDB_CODE_SUCCESS == toDoubleEx(pToken->z, pToken->n, pToken->type, (double*)&VALUE_GET_TRIVIAL_DATUM(&pVal->value)))) { int8_t v = *(double*)&VALUE_GET_TRIVIAL_DATUM(&pVal->value) == 0 ? FALSE_VALUE : TRUE_VALUE; valueSetDatum(&pVal->value, TSDB_DATA_TYPE_BOOL, &v, tDataTypes[TSDB_DATA_TYPE_BOOL].bytes); } else { return buildSyntaxErrMsg(&pCxt->msg, "invalid bool data", pToken->z); } break; } case TSDB_DATA_TYPE_TINYINT: { int32_t code = toIntegerEx(pToken->z, pToken->n, pToken->type, &VALUE_GET_TRIVIAL_DATUM(&pVal->value)); if (TSDB_CODE_SUCCESS != code) { return buildSyntaxErrMsg(&pCxt->msg, "invalid tinyint data", pToken->z); } else if (!IS_VALID_TINYINT(VALUE_GET_TRIVIAL_DATUM(&pVal->value))) { return buildSyntaxErrMsg(&pCxt->msg, "tinyint data overflow", pToken->z); } break; } case TSDB_DATA_TYPE_UTINYINT: { int32_t code = toUIntegerEx(pToken->z, pToken->n, pToken->type, (uint64_t*)&VALUE_GET_TRIVIAL_DATUM(&pVal->value)); if (TSDB_CODE_SUCCESS != code) { return buildSyntaxErrMsg(&pCxt->msg, "invalid unsigned tinyint data", pToken->z); } else if (VALUE_GET_TRIVIAL_DATUM(&pVal->value) > UINT8_MAX) { return buildSyntaxErrMsg(&pCxt->msg, "unsigned tinyint data overflow", pToken->z); } break; } case TSDB_DATA_TYPE_SMALLINT: { int32_t code = toIntegerEx(pToken->z, pToken->n, pToken->type, &VALUE_GET_TRIVIAL_DATUM(&pVal->value)); if (TSDB_CODE_SUCCESS != code) { return buildSyntaxErrMsg(&pCxt->msg, "invalid smallint data", pToken->z); } else if (!IS_VALID_SMALLINT(VALUE_GET_TRIVIAL_DATUM(&pVal->value))) { return buildSyntaxErrMsg(&pCxt->msg, "smallint data overflow", pToken->z); } break; } case TSDB_DATA_TYPE_USMALLINT: { int32_t code = toUIntegerEx(pToken->z, pToken->n, pToken->type, &VALUE_GET_TRIVIAL_DATUM(&pVal->value)); if (TSDB_CODE_SUCCESS != code) { return buildSyntaxErrMsg(&pCxt->msg, "invalid unsigned smallint data", pToken->z); } else if (VALUE_GET_TRIVIAL_DATUM(&pVal->value) > UINT16_MAX) { return buildSyntaxErrMsg(&pCxt->msg, "unsigned smallint data overflow", pToken->z); } break; } case TSDB_DATA_TYPE_INT: { int32_t code = toIntegerEx(pToken->z, pToken->n, pToken->type, &VALUE_GET_TRIVIAL_DATUM(&pVal->value)); if (TSDB_CODE_SUCCESS != code) { return buildSyntaxErrMsg(&pCxt->msg, "invalid int data", pToken->z); } else if (!IS_VALID_INT(VALUE_GET_TRIVIAL_DATUM(&pVal->value))) { return buildSyntaxErrMsg(&pCxt->msg, "int data overflow", pToken->z); } break; } case TSDB_DATA_TYPE_UINT: { int32_t code = toUIntegerEx(pToken->z, pToken->n, pToken->type, &VALUE_GET_TRIVIAL_DATUM(&pVal->value)); if (TSDB_CODE_SUCCESS != code) { return buildSyntaxErrMsg(&pCxt->msg, "invalid unsigned int data", pToken->z); } else if (VALUE_GET_TRIVIAL_DATUM(&pVal->value) > UINT32_MAX) { return buildSyntaxErrMsg(&pCxt->msg, "unsigned int data overflow", pToken->z); } break; } case TSDB_DATA_TYPE_BIGINT: { int32_t code = toIntegerEx(pToken->z, pToken->n, pToken->type, &VALUE_GET_TRIVIAL_DATUM(&pVal->value)); if (TSDB_CODE_SUCCESS != code) { return buildSyntaxErrMsg(&pCxt->msg, "invalid bigint data", pToken->z); } break; } case TSDB_DATA_TYPE_UBIGINT: { int32_t code = toUIntegerEx(pToken->z, pToken->n, pToken->type, &VALUE_GET_TRIVIAL_DATUM(&pVal->value)); if (TSDB_CODE_SUCCESS != code) { return buildSyntaxErrMsg(&pCxt->msg, "invalid unsigned bigint data", pToken->z); } break; } case TSDB_DATA_TYPE_FLOAT: { double dv; int32_t code = toDoubleEx(pToken->z, pToken->n, pToken->type, &dv); if (TSDB_CODE_SUCCESS != code) { return buildSyntaxErrMsg(&pCxt->msg, "illegal float data", pToken->z); } if (dv > FLT_MAX || dv < -FLT_MAX || isinf(dv) || isnan(dv)) { return buildSyntaxErrMsg(&pCxt->msg, "illegal float data", pToken->z); } float f = dv; valueSetDatum(&pVal->value, TSDB_DATA_TYPE_FLOAT, &f, sizeof(f)); break; } case TSDB_DATA_TYPE_DOUBLE: { double dv; int32_t code = toDoubleEx(pToken->z, pToken->n, pToken->type, &dv); if (TSDB_CODE_SUCCESS != code) { return buildSyntaxErrMsg(&pCxt->msg, "illegal float data", pToken->z); } if (isinf(dv) || isnan(dv)) { return buildSyntaxErrMsg(&pCxt->msg, "illegal double data", pToken->z); } VALUE_SET_TRIVIAL_DATUM(&pVal->value, (*(int64_t*)&dv)); break; } case TSDB_DATA_TYPE_BINARY: { // Too long values will raise the invalid sql error message if (pToken->n + VARSTR_HEADER_SIZE > pSchema->bytes) { return generateSyntaxErrMsg(&pCxt->msg, TSDB_CODE_PAR_VALUE_TOO_LONG, pSchema->name); } pVal->value.pData = taosMemoryMalloc(pToken->n); if (NULL == pVal->value.pData) { return terrno; } memcpy(pVal->value.pData, pToken->z, pToken->n); pVal->value.nData = pToken->n; break; } case TSDB_DATA_TYPE_VARBINARY: { int32_t code = parseVarbinary(pToken, &pVal->value.pData, &pVal->value.nData, pSchema->bytes); if (code != TSDB_CODE_SUCCESS) { return generateSyntaxErrMsg(&pCxt->msg, code, pSchema->name); } break; } case TSDB_DATA_TYPE_NCHAR: { // if the converted output len is over than pColumnModel->bytes, return error: 'Argument list too long' int32_t len = 0; int64_t realLen = pToken->n << 2; if (realLen > pSchema->bytes - VARSTR_HEADER_SIZE) realLen = pSchema->bytes - VARSTR_HEADER_SIZE; char* pUcs4 = taosMemoryMalloc(realLen); if (NULL == pUcs4) { return terrno; } if (!taosMbsToUcs4(pToken->z, pToken->n, (TdUcs4*)pUcs4, realLen, &len, pCxt->pComCxt->charsetCxt)) { taosMemoryFree(pUcs4); if (terrno == TAOS_SYSTEM_ERROR(E2BIG)) { return generateSyntaxErrMsg(&pCxt->msg, TSDB_CODE_PAR_VALUE_TOO_LONG, pSchema->name); } char buf[512] = {0}; snprintf(buf, tListLen(buf), "%s", strerror(terrno)); return buildSyntaxErrMsg(&pCxt->msg, buf, pToken->z); } pVal->value.pData = pUcs4; pVal->value.nData = len; break; } case TSDB_DATA_TYPE_JSON: { if (pToken->n > (TSDB_MAX_JSON_TAG_LEN - VARSTR_HEADER_SIZE) / TSDB_NCHAR_SIZE) { return buildSyntaxErrMsg(&pCxt->msg, "json string too long than 4095", pToken->z); } pVal->value.pData = taosMemoryMalloc(pToken->n); if (NULL == pVal->value.pData) { return terrno; } memcpy(pVal->value.pData, pToken->z, pToken->n); pVal->value.nData = pToken->n; break; } case TSDB_DATA_TYPE_GEOMETRY: { int32_t code = TSDB_CODE_FAILED; unsigned char* output = NULL; size_t size = 0; code = parseGeometry(pToken, &output, &size); if (code != TSDB_CODE_SUCCESS) { code = buildSyntaxErrMsg(&pCxt->msg, getGeosErrMsg(code), pToken->z); } // Too long values will raise the invalid sql error message else if (size + VARSTR_HEADER_SIZE > pSchema->bytes) { code = generateSyntaxErrMsg(&pCxt->msg, TSDB_CODE_PAR_VALUE_TOO_LONG, pSchema->name); } else { pVal->value.pData = taosMemoryMalloc(size); if (NULL == pVal->value.pData) { code = terrno; } else { memcpy(pVal->value.pData, output, size); pVal->value.nData = size; } } geosFreeBuffer(output); if (code != TSDB_CODE_SUCCESS) { return code; } break; } case TSDB_DATA_TYPE_TIMESTAMP: { if (parseTime(pSql, pToken, timePrec, &VALUE_GET_TRIVIAL_DATUM(&pVal->value), &pCxt->msg, pCxt->pComCxt->timezone) != TSDB_CODE_SUCCESS) { return buildSyntaxErrMsg(&pCxt->msg, "invalid timestamp", pToken->z); } break; } case TSDB_DATA_TYPE_DECIMAL: { if (!pExtSchema) { qError("Decimal type without ext schema info, cannot parse decimal values"); return TSDB_CODE_PAR_INTERNAL_ERROR; } uint8_t precision = 0, scale = 0; decimalFromTypeMod(pExtSchema->typeMod, &precision, &scale); Decimal128 dec = {0}; int32_t code = decimal128FromStr(pToken->z, pToken->n, precision, scale, &dec); if (TSDB_CODE_SUCCESS != code) { return code; } // precision check // scale auto fit code = decimal128ToDataVal(&dec, &pVal->value); if (TSDB_CODE_SUCCESS != code) { return code; } break; } case TSDB_DATA_TYPE_DECIMAL64: { if (!pExtSchema) { qError("Decimal type without ext schema info, cannot parse decimal values"); return TSDB_CODE_PAR_INTERNAL_ERROR; } uint8_t precision = 0, scale = 0; decimalFromTypeMod(pExtSchema->typeMod, &precision, &scale); Decimal64 dec = {0}; int32_t code = decimal64FromStr(pToken->z, pToken->n, precision, scale, &dec); if (TSDB_CODE_SUCCESS != code) { return code; } code = decimal64ToDataVal(&dec, &pVal->value); if (TSDB_CODE_SUCCESS != code) { return code; } break; } default: return TSDB_CODE_FAILED; } pVal->flag = CV_FLAG_VALUE; return TSDB_CODE_SUCCESS; } static int32_t parseValueToken(SInsertParseContext* pCxt, const char** pSql, SToken* pToken, SSchema* pSchema, const SSchemaExt* pExtSchema, int16_t timePrec, SColVal* pVal) { int32_t code = checkAndTrimValue(pToken, pCxt->tmpTokenBuf, &pCxt->msg, pSchema->type); if (TSDB_CODE_SUCCESS == code && isNullValue(pSchema->type, pToken)) { if (TSDB_DATA_TYPE_TIMESTAMP == pSchema->type && PRIMARYKEY_TIMESTAMP_COL_ID == pSchema->colId) { return buildSyntaxErrMsg(&pCxt->msg, "Primary timestamp column should not be null", pToken->z); } pVal->flag = CV_FLAG_NULL; return TSDB_CODE_SUCCESS; } if (TSDB_CODE_SUCCESS == code) { if (pToken->n == 0 && IS_NUMERIC_TYPE(pSchema->type)) { return buildSyntaxErrMsg(&pCxt->msg, "invalid numeric data", pToken->z); } code = parseValueTokenImpl(pCxt, pSql, pToken, pSchema, pExtSchema, timePrec, pVal); } return code; } static void clearColValArray(SArray* pCols) { int32_t num = taosArrayGetSize(pCols); for (int32_t i = 0; i < num; ++i) { SColVal* pCol = taosArrayGet(pCols, i); if (IS_VAR_DATA_TYPE(pCol->value.type) || pCol->value.type == TSDB_DATA_TYPE_DECIMAL) { taosMemoryFreeClear(pCol->value.pData); } } } typedef struct SStbRowsDataContext { SName stbName; STableMeta* pStbMeta; SNode* pTagCond; SBoundColInfo boundColsInfo; // the following fields are for each stb row SArray* aTagVals; SArray* aColVals; SArray* aTagNames; SName ctbName; STag* pTag; STableMeta* pCtbMeta; SVCreateTbReq* pCreateCtbReq; bool hasTimestampTag; bool isJsonTag; } SStbRowsDataContext; typedef union SRowsDataContext { STableDataCxt* pTableDataCxt; SStbRowsDataContext* pStbRowsCxt; } SRowsDataContext; int32_t parseTbnameToken(SMsgBuf* pMsgBuf, char* tname, SToken* pToken, bool* pFoundCtbName) { *pFoundCtbName = false; if (isNullValue(TSDB_DATA_TYPE_BINARY, pToken)) { return buildInvalidOperationMsg(pMsgBuf, "tbname can not be null value"); } if (pToken->n > 0) { if (pToken->n <= TSDB_TABLE_NAME_LEN - 1) { for (int i = 0; i < pToken->n; ++i) { if (pToken->z[i] == '.') { return buildInvalidOperationMsg(pMsgBuf, "tbname can not contain '.'"); } else { tname[i] = pToken->z[i]; } } tname[pToken->n] = '\0'; *pFoundCtbName = true; } else { return buildInvalidOperationMsg(pMsgBuf, "tbname is too long"); } } else { return buildInvalidOperationMsg(pMsgBuf, "tbname can not be empty"); } return TSDB_CODE_SUCCESS; } static int32_t processCtbTagsAfterCtbName(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, SStbRowsDataContext* pStbRowsCxt, bool ctbFirst, const SToken* tagTokens, SSchema* const* tagSchemas, int numOfTagTokens) { int32_t code = TSDB_CODE_SUCCESS; uint8_t precision = pStmt->pTableMeta->tableInfo.precision; if (code == TSDB_CODE_SUCCESS && ctbFirst) { for (int32_t i = 0; code == TSDB_CODE_SUCCESS && i < numOfTagTokens; ++i) { SToken* pTagToken = (SToken*)(tagTokens + i); SSchema* pTagSchema = tagSchemas[i]; code = checkAndTrimValue(pTagToken, pCxt->tmpTokenBuf, &pCxt->msg, pTagSchema->type); if (code == TSDB_CODE_SUCCESS && TK_NK_VARIABLE == pTagToken->type) { code = buildInvalidOperationMsg(&pCxt->msg, "not expected tag"); } if (code == TSDB_CODE_SUCCESS) { code = parseTagValue(&pCxt->msg, NULL, precision, pTagSchema, pTagToken, pStbRowsCxt->aTagNames, pStbRowsCxt->aTagVals, &pStbRowsCxt->pTag, pCxt->pComCxt->timezone, pCxt->pComCxt->charsetCxt); } } if (code == TSDB_CODE_SUCCESS && !pStbRowsCxt->isJsonTag) { code = tTagNew(pStbRowsCxt->aTagVals, 1, false, &pStbRowsCxt->pTag); } } if (code == TSDB_CODE_SUCCESS && pStbRowsCxt->pTagCond) { code = checkSubtablePrivilege(pStbRowsCxt->aTagVals, pStbRowsCxt->aTagNames, &pStbRowsCxt->pTagCond); } return code; } static int32_t doGetStbRowValues(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, const char** ppSql, SStbRowsDataContext* pStbRowsCxt, SToken* pToken, const SBoundColInfo* pCols, const SSchema* pSchemas, const SSchemaExt* pExtSchemas, SToken* tagTokens, SSchema** tagSchemas, int* pNumOfTagTokens, bool* bFoundTbName, bool* setCtbName, SBoundColInfo* ctbCols) { int32_t code = TSDB_CODE_SUCCESS; SArray* pTagNames = pStbRowsCxt->aTagNames; SArray* pTagVals = pStbRowsCxt->aTagVals; bool canParseTagsAfter = !pStbRowsCxt->pTagCond && !pStbRowsCxt->hasTimestampTag; int32_t numOfCols = getNumOfColumns(pStbRowsCxt->pStbMeta); int32_t numOfTags = getNumOfTags(pStbRowsCxt->pStbMeta); int32_t tbnameIdx = getTbnameSchemaIndex(pStbRowsCxt->pStbMeta); uint8_t precision = getTableInfo(pStbRowsCxt->pStbMeta).precision; int tag_index = 0; int col_index = 0; for (int i = 0; i < pCols->numOfBound && (code) == TSDB_CODE_SUCCESS; ++i) { const char* pTmpSql = *ppSql; bool ignoreComma = false; NEXT_TOKEN_WITH_PREV_EXT(*ppSql, *pToken, &ignoreComma); if (ignoreComma) { code = buildSyntaxErrMsg(&pCxt->msg, "invalid data or symbol", pTmpSql); break; } if (TK_NK_RP == pToken->type) { code = generateSyntaxErrMsg(&pCxt->msg, TSDB_CODE_PAR_INVALID_COLUMNS_NUM); break; } if (TK_NK_QUESTION == pToken->type) { if (!pCxt->pComCxt->isStmtBind && i != 0) { return buildInvalidOperationMsg(&pCxt->msg, "not support mixed bind and non-bind values"); } if (pCxt->pComCxt->pStmtCb == NULL) { return buildInvalidOperationMsg(&pCxt->msg, "symbol ? only support in stmt mode"); } pCxt->isStmtBind = true; if (pCols->pColIndex[i] == tbnameIdx) { *bFoundTbName = true; char* tbName = NULL; if ((*pCxt->pComCxt->pStmtCb->getTbNameFn)(pCxt->pComCxt->pStmtCb->pStmt, &tbName) == TSDB_CODE_SUCCESS) { tstrncpy(pStbRowsCxt->ctbName.tname, tbName, sizeof(pStbRowsCxt->ctbName.tname)); tstrncpy(pStmt->usingTableName.tname, pStmt->targetTableName.tname, sizeof(pStmt->usingTableName.tname)); tstrncpy(pStmt->targetTableName.tname, tbName, sizeof(pStmt->targetTableName.tname)); tstrncpy(pStmt->usingTableName.dbname, pStmt->targetTableName.dbname, sizeof(pStmt->usingTableName.dbname)); pStmt->usingTableName.type = 1; pStmt->pTableMeta->tableType = TSDB_CHILD_TABLE; // set the table type to child table for parse cache *setCtbName = true; } } else if (pCols->pColIndex[i] < numOfCols) { // bind column if (ctbCols->pColIndex == NULL) { ctbCols->pColIndex = taosMemoryCalloc(numOfCols, sizeof(int16_t)); if (NULL == ctbCols->pColIndex) { return terrno; } } ctbCols->pColIndex[col_index++] = pCols->pColIndex[i]; ctbCols->numOfBound++; ctbCols->numOfCols++; } else if (pCols->pColIndex[i] < tbnameIdx) { if (pCxt->tags.pColIndex == NULL) { pCxt->tags.pColIndex = taosMemoryCalloc(numOfTags, sizeof(int16_t)); if (NULL == pCxt->tags.pColIndex) { return terrno; } } if (!(tag_index < numOfTags)) { return buildInvalidOperationMsg(&pCxt->msg, "not expected numOfTags"); } pStmt->usingTableProcessing = true; pCxt->tags.pColIndex[tag_index++] = pCols->pColIndex[i] - numOfCols; pCxt->tags.mixTagsCols = true; pCxt->tags.numOfBound++; pCxt->tags.numOfCols++; } else { return buildInvalidOperationMsg(&pCxt->msg, "not expected numOfBound"); } } else { if (pCxt->pComCxt->isStmtBind) { return buildInvalidOperationMsg(&pCxt->msg, "not support mixed bind and non-bind values"); } if (pCols->pColIndex[i] < numOfCols) { const SSchema* pSchema = &pSchemas[pCols->pColIndex[i]]; const SSchemaExt* pExtSchema = pExtSchemas + pCols->pColIndex[i]; SColVal* pVal = taosArrayGet(pStbRowsCxt->aColVals, pCols->pColIndex[i]); code = parseValueToken(pCxt, ppSql, pToken, (SSchema*)pSchema, pExtSchema, precision, pVal); if (TK_NK_VARIABLE == pToken->type) { code = buildInvalidOperationMsg(&pCxt->msg, "not expected row value"); } } else if (pCols->pColIndex[i] < tbnameIdx) { const SSchema* pTagSchema = &pSchemas[pCols->pColIndex[i]]; if (canParseTagsAfter) { tagTokens[(*pNumOfTagTokens)] = *pToken; tagSchemas[(*pNumOfTagTokens)] = (SSchema*)pTagSchema; ++(*pNumOfTagTokens); } else { code = checkAndTrimValue(pToken, pCxt->tmpTokenBuf, &pCxt->msg, pTagSchema->type); if (code == TSDB_CODE_SUCCESS && TK_NK_VARIABLE == pToken->type) { code = buildInvalidOperationMsg(&pCxt->msg, "not expected row value"); } if (code == TSDB_CODE_SUCCESS) { code = parseTagValue(&pCxt->msg, ppSql, precision, (SSchema*)pTagSchema, pToken, pTagNames, pTagVals, &pStbRowsCxt->pTag, pCxt->pComCxt->timezone, pCxt->pComCxt->charsetCxt); } } } else if (pCols->pColIndex[i] == tbnameIdx) { code = checkAndTrimValue(pToken, pCxt->tmpTokenBuf, &pCxt->msg, TSDB_DATA_TYPE_BINARY); if (TK_NK_VARIABLE == pToken->type) { code = buildInvalidOperationMsg(&pCxt->msg, "not expected tbname"); } if (code == TSDB_CODE_SUCCESS) { code = parseTbnameToken(&pCxt->msg, pStbRowsCxt->ctbName.tname, pToken, bFoundTbName); } } } if (code == TSDB_CODE_SUCCESS && i < pCols->numOfBound - 1) { NEXT_VALID_TOKEN(*ppSql, *pToken); if (TK_NK_COMMA != pToken->type) { code = buildSyntaxErrMsg(&pCxt->msg, ", expected", pToken->z); } } } return code; } static int32_t getStbRowValues(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, const char** ppSql, SStbRowsDataContext* pStbRowsCxt, bool* pGotRow, SToken* pToken, bool* pCtbFirst, bool* setCtbName, SBoundColInfo* ctbCols) { SBoundColInfo* pCols = &pStbRowsCxt->boundColsInfo; SSchema* pSchemas = getTableColumnSchema(pStbRowsCxt->pStbMeta); SSchemaExt* pExtSchemas = getTableColumnExtSchema(pStbRowsCxt->pStbMeta); bool bFoundTbName = false; const char* pOrigSql = *ppSql; int32_t code = TSDB_CODE_SUCCESS; SToken tagTokens[TSDB_MAX_TAGS] = {0}; SSchema* tagSchemas[TSDB_MAX_TAGS] = {0}; int numOfTagTokens = 0; code = doGetStbRowValues(pCxt, pStmt, ppSql, pStbRowsCxt, pToken, pCols, pSchemas, pExtSchemas, tagTokens, tagSchemas, &numOfTagTokens, &bFoundTbName, setCtbName, ctbCols); if (code != TSDB_CODE_SUCCESS) { return code; } if (!bFoundTbName) { code = buildSyntaxErrMsg(&pCxt->msg, "tbname value expected", pOrigSql); } bool ctbFirst = true; char ctbFName[TSDB_TABLE_FNAME_LEN]; if (code == TSDB_CODE_SUCCESS) { code = tNameExtractFullName(&pStbRowsCxt->ctbName, ctbFName); } if (TSDB_CODE_SUCCESS == code) { STableMeta** pCtbMeta = taosHashGet(pStmt->pSubTableHashObj, ctbFName, strlen(ctbFName)); ctbFirst = (pCtbMeta == NULL); if (!ctbFirst) { pStbRowsCxt->pCtbMeta->uid = (*pCtbMeta)->uid; pStbRowsCxt->pCtbMeta->vgId = (*pCtbMeta)->vgId; } *pCtbFirst = ctbFirst; } if (code == TSDB_CODE_SUCCESS) { code = processCtbTagsAfterCtbName(pCxt, pStmt, pStbRowsCxt, ctbFirst, tagTokens, tagSchemas, numOfTagTokens); } if (code == TSDB_CODE_SUCCESS) { *pGotRow = true; } return code; } static int32_t processCtbAutoCreationAndCtbMeta(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, SStbRowsDataContext* pStbRowsCxt) { int32_t code = TSDB_CODE_SUCCESS; pStbRowsCxt->pCreateCtbReq = taosMemoryCalloc(1, sizeof(SVCreateTbReq)); if (pStbRowsCxt->pCreateCtbReq == NULL) { code = terrno; } if (code == TSDB_CODE_SUCCESS) { code = insBuildCreateTbReq(pStbRowsCxt->pCreateCtbReq, pStbRowsCxt->ctbName.tname, pStbRowsCxt->pTag, pStbRowsCxt->pStbMeta->uid, pStbRowsCxt->stbName.tname, pStbRowsCxt->aTagNames, getNumOfTags(pStbRowsCxt->pStbMeta), TSDB_DEFAULT_TABLE_TTL); pStbRowsCxt->pTag = NULL; } if (code == TSDB_CODE_SUCCESS) { char ctbFName[TSDB_TABLE_FNAME_LEN]; code = tNameExtractFullName(&pStbRowsCxt->ctbName, ctbFName); SVgroupInfo vg; SRequestConnInfo conn = {.pTrans = pCxt->pComCxt->pTransporter, .requestId = pCxt->pComCxt->requestId, .requestObjRefId = pCxt->pComCxt->requestRid, .mgmtEps = pCxt->pComCxt->mgmtEpSet}; if (TSDB_CODE_SUCCESS == code) { code = catalogGetTableHashVgroup(pCxt->pComCxt->pCatalog, &conn, &pStbRowsCxt->ctbName, &vg); } if (code == TSDB_CODE_SUCCESS) { code = taosHashPut(pStmt->pVgroupsHashObj, (const char*)(&vg.vgId), sizeof(vg.vgId), &vg, sizeof(vg)); } STableMeta* pBackup = NULL; if (TSDB_CODE_SUCCESS == code) { pStbRowsCxt->pCtbMeta->uid = taosHashGetSize(pStmt->pSubTableHashObj) + 1; pStbRowsCxt->pCtbMeta->vgId = vg.vgId; code = cloneTableMeta(pStbRowsCxt->pCtbMeta, &pBackup); } if (TSDB_CODE_SUCCESS == code) { code = taosHashPut(pStmt->pSubTableHashObj, ctbFName, strlen(ctbFName), &pBackup, POINTER_BYTES); } if (TSDB_CODE_SUCCESS == code) { code = collectUseTable(&pStbRowsCxt->ctbName, pStmt->pTableNameHashObj); } } return code; } static void clearStbRowsDataContext(SStbRowsDataContext* pStbRowsCxt) { if (pStbRowsCxt == NULL) return; taosArrayClear(pStbRowsCxt->aTagNames); for (int i = 0; i < taosArrayGetSize(pStbRowsCxt->aTagVals); ++i) { STagVal* p = (STagVal*)taosArrayGet(pStbRowsCxt->aTagVals, i); if (IS_VAR_DATA_TYPE(p->type)) { taosMemoryFreeClear(p->pData); } } taosArrayClear(pStbRowsCxt->aTagVals); clearColValArray(pStbRowsCxt->aColVals); tTagFree(pStbRowsCxt->pTag); pStbRowsCxt->pTag = NULL; tdDestroySVCreateTbReq(pStbRowsCxt->pCreateCtbReq); taosMemoryFreeClear(pStbRowsCxt->pCreateCtbReq); } static int32_t parseStbBoundInfo(SVnodeModifyOpStmt* pStmt, SStbRowsDataContext* pStbRowsCxt, STableDataCxt** ppTableDataCxt) { char tbFName[TSDB_TABLE_FNAME_LEN]; int32_t code = tNameExtractFullName(&pStmt->targetTableName, tbFName); if (TSDB_CODE_SUCCESS != code) { return code; } if (pStmt->usingTableProcessing) { pStmt->pTableMeta->uid = 0; } code = insGetTableDataCxt(pStmt->pTableBlockHashObj, tbFName, strlen(tbFName), pStmt->pTableMeta, &pStmt->pCreateTblReq, ppTableDataCxt, false, true); if (code != TSDB_CODE_SUCCESS) { return code; } insDestroyBoundColInfo(&((*ppTableDataCxt)->boundColsInfo)); (*ppTableDataCxt)->boundColsInfo = pStbRowsCxt->boundColsInfo; (*ppTableDataCxt)->boundColsInfo.pColIndex = taosMemoryCalloc(pStbRowsCxt->boundColsInfo.numOfBound, sizeof(int16_t)); if (NULL == (*ppTableDataCxt)->boundColsInfo.pColIndex) { return terrno; } (void)memcpy((*ppTableDataCxt)->boundColsInfo.pColIndex, pStbRowsCxt->boundColsInfo.pColIndex, sizeof(int16_t) * pStmt->pStbRowsCxt->boundColsInfo.numOfBound); return TSDB_CODE_SUCCESS; } static int32_t parseOneStbRow(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, const char** ppSql, SStbRowsDataContext* pStbRowsCxt, bool* pGotRow, SToken* pToken, STableDataCxt** ppTableDataCxt) { bool bFirstTable = false; bool setCtbName = false; SBoundColInfo ctbCols = {0}; int32_t code = getStbRowValues(pCxt, pStmt, ppSql, pStbRowsCxt, pGotRow, pToken, &bFirstTable, &setCtbName, &ctbCols); if (!setCtbName && pCxt->isStmtBind) { taosMemoryFreeClear(ctbCols.pColIndex); return parseStbBoundInfo(pStmt, pStbRowsCxt, ppTableDataCxt); } if (code != TSDB_CODE_SUCCESS || !*pGotRow) { return code; } if (code == TSDB_CODE_SUCCESS && bFirstTable) { code = processCtbAutoCreationAndCtbMeta(pCxt, pStmt, pStbRowsCxt); } if (code == TSDB_CODE_SUCCESS) { if (pCxt->isStmtBind) { char ctbFName[TSDB_TABLE_FNAME_LEN]; code = tNameExtractFullName(&pStbRowsCxt->ctbName, ctbFName); if (code != TSDB_CODE_SUCCESS) { return code; } code = insGetTableDataCxt(pStmt->pTableBlockHashObj, ctbFName, strlen(ctbFName), pStbRowsCxt->pCtbMeta, &pStbRowsCxt->pCreateCtbReq, ppTableDataCxt, true, true); } else { code = insGetTableDataCxt(pStmt->pTableBlockHashObj, &pStbRowsCxt->pCtbMeta->uid, sizeof(pStbRowsCxt->pCtbMeta->uid), pStbRowsCxt->pCtbMeta, &pStbRowsCxt->pCreateCtbReq, ppTableDataCxt, false, true); } } if (code == TSDB_CODE_SUCCESS) { if (pCxt->isStmtBind) { int32_t tbnameIdx = getTbnameSchemaIndex(pStbRowsCxt->pStbMeta); code = initTableColSubmitDataWithBoundInfo(*ppTableDataCxt, ctbCols); } else { code = initTableColSubmitData(*ppTableDataCxt); } } if (code == TSDB_CODE_SUCCESS && !pCxt->isStmtBind) { SRow** pRow = taosArrayReserve((*ppTableDataCxt)->pData->aRowP, 1); code = tRowBuild(pStbRowsCxt->aColVals, (*ppTableDataCxt)->pSchema, pRow); if (TSDB_CODE_SUCCESS == code) { SRowKey key; tRowGetKey(*pRow, &key); insCheckTableDataOrder(*ppTableDataCxt, &key); } } if (code == TSDB_CODE_SUCCESS) { *pGotRow = true; } clearStbRowsDataContext(pStbRowsCxt); return code; } static int parseOneRow(SInsertParseContext* pCxt, const char** pSql, STableDataCxt* pTableCxt, bool* pGotRow, SToken* pToken) { SBoundColInfo* pCols = &pTableCxt->boundColsInfo; SSchema* pSchemas = getTableColumnSchema(pTableCxt->pMeta); const SSchemaExt* pExtSchemas = getTableColumnExtSchema(pTableCxt->pMeta); int32_t code = TSDB_CODE_SUCCESS; // 1. set the parsed value from sql string for (int i = 0; i < pCols->numOfBound && TSDB_CODE_SUCCESS == code; ++i) { const char* pOrigSql = *pSql; bool ignoreComma = false; NEXT_TOKEN_WITH_PREV_EXT(*pSql, *pToken, &ignoreComma); if (ignoreComma) { code = buildSyntaxErrMsg(&pCxt->msg, "invalid data or symbol", pOrigSql); break; } SSchema* pSchema = &pSchemas[pCols->pColIndex[i]]; const SSchemaExt* pExtSchema = pExtSchemas + pCols->pColIndex[i]; SColVal* pVal = taosArrayGet(pTableCxt->pValues, pCols->pColIndex[i]); if (pToken->type == TK_NK_QUESTION) { pCxt->isStmtBind = true; if (NULL == pCxt->pComCxt->pStmtCb) { code = buildSyntaxErrMsg(&pCxt->msg, "? only used in stmt", pToken->z); break; } } else { if (TK_NK_RP == pToken->type) { code = generateSyntaxErrMsg(&pCxt->msg, TSDB_CODE_PAR_INVALID_COLUMNS_NUM); break; } if (pCxt->isStmtBind) { code = buildInvalidOperationMsg(&pCxt->msg, "stmt bind param does not support normal value in sql"); break; } if (TSDB_CODE_SUCCESS == code) { code = parseValueToken(pCxt, pSql, pToken, pSchema, pExtSchema, getTableInfo(pTableCxt->pMeta).precision, pVal); } } if (TSDB_CODE_SUCCESS == code && i < pCols->numOfBound - 1) { NEXT_VALID_TOKEN(*pSql, *pToken); if (TK_NK_COMMA != pToken->type) { code = buildSyntaxErrMsg(&pCxt->msg, ", expected", pToken->z); } } } if (TSDB_CODE_SUCCESS == code && !pCxt->isStmtBind) { SRow** pRow = taosArrayReserve(pTableCxt->pData->aRowP, 1); code = tRowBuild(pTableCxt->pValues, pTableCxt->pSchema, pRow); if (TSDB_CODE_SUCCESS == code) { SRowKey key; tRowGetKey(*pRow, &key); insCheckTableDataOrder(pTableCxt, &key); } } if (TSDB_CODE_SUCCESS == code && !pCxt->isStmtBind) { *pGotRow = true; } clearColValArray(pTableCxt->pValues); return code; } // pSql -> (field1_value, ...) [(field1_value2, ...) ...] static int32_t parseValues(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, SRowsDataContext rowsDataCxt, int32_t* pNumOfRows, SToken* pToken) { int32_t code = TSDB_CODE_SUCCESS; (*pNumOfRows) = 0; while (TSDB_CODE_SUCCESS == code) { int32_t index = 0; NEXT_TOKEN_KEEP_SQL(pStmt->pSql, *pToken, index); if (TK_NK_LP != pToken->type) { break; } pStmt->pSql += index; bool gotRow = false; if (TSDB_CODE_SUCCESS == code) { if (!pStmt->stbSyntax) { code = parseOneRow(pCxt, &pStmt->pSql, rowsDataCxt.pTableDataCxt, &gotRow, pToken); } else { // foreach subtable STableDataCxt* pTableDataCxt = NULL; code = parseOneStbRow(pCxt, pStmt, &pStmt->pSql, rowsDataCxt.pStbRowsCxt, &gotRow, pToken, &pTableDataCxt); } } if (TSDB_CODE_SUCCESS == code) { NEXT_VALID_TOKEN(pStmt->pSql, *pToken); if (TK_NK_COMMA == pToken->type) { code = generateSyntaxErrMsg(&pCxt->msg, TSDB_CODE_PAR_INVALID_COLUMNS_NUM); } else if (TK_NK_RP != pToken->type) { code = buildSyntaxErrMsg(&pCxt->msg, ") expected", pToken->z); } } if (TSDB_CODE_SUCCESS == code && gotRow) { (*pNumOfRows)++; } } if (TSDB_CODE_SUCCESS == code && 0 == (*pNumOfRows) && (!TSDB_QUERY_HAS_TYPE(pStmt->insertType, TSDB_QUERY_TYPE_STMT_INSERT))) { code = buildSyntaxErrMsg(&pCxt->msg, "no any data points", NULL); } return code; } // VALUES (field1_value, ...) [(field1_value2, ...) ...] static int32_t parseValuesClause(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, SRowsDataContext rowsDataContext, SToken* pToken) { int32_t numOfRows = 0; int32_t code = parseValues(pCxt, pStmt, rowsDataContext, &numOfRows, pToken); if (TSDB_CODE_SUCCESS == code) { pStmt->totalRowsNum += numOfRows; pStmt->totalTbNum += 1; TSDB_QUERY_SET_TYPE(pStmt->insertType, TSDB_QUERY_TYPE_INSERT); } return code; } // Simplified CSV parser - only handles newlines within quotes static int32_t csvParserReadLine(SCsvParser* parser) { if (!parser) { return TSDB_CODE_INVALID_PARA; } size_t lineLen = 0; bool inQuotes = false; char currentQuote = '\0'; // Track which quote character we're inside int32_t code = TSDB_CODE_SUCCESS; while (true) { // Fill buffer if needed if (parser->bufferPos >= parser->bufferLen) { code = csvParserFillBuffer(parser); if (code != TSDB_CODE_SUCCESS) { break; } if (parser->bufferPos >= parser->bufferLen && parser->eof) { // End of file if (lineLen == 0) { code = TSDB_CODE_TSC_QUERY_CANCELLED; // Use this to indicate EOF } break; } } char ch = parser->buffer[parser->bufferPos++]; // Handle quotes - support both single and double quotes if (!inQuotes && (ch == CSV_QUOTE_SINGLE || ch == CSV_QUOTE_DOUBLE)) { // Starting a quoted section inQuotes = true; currentQuote = ch; } else if (inQuotes && ch == currentQuote) { // Check for escaped quote (double quote) if (parser->bufferPos < parser->bufferLen && parser->buffer[parser->bufferPos] == currentQuote) { // Escaped quote - keep both quotes in line for subsequent processing // Ensure enough space for both quote characters code = csvParserExpandLineBuffer(parser, lineLen + 2); if (code != TSDB_CODE_SUCCESS) { break; } // Add the first quote character to the line parser->lineBuffer[lineLen++] = ch; // Consume and add the second quote character parser->bufferPos++; ch = parser->buffer[parser->bufferPos - 1]; // The second quote parser->lineBuffer[lineLen++] = ch; continue; } else { // End of quoted section inQuotes = false; currentQuote = '\0'; } } // Handle newlines if (ch == '\n' && !inQuotes) { // End of line (not inside quotes) break; } // Skip \r characters only when outside quotes if (ch == '\r' && !inQuotes) { continue; } // Expand buffer if needed code = csvParserExpandLineBuffer(parser, lineLen + 1); if (code != TSDB_CODE_SUCCESS) { break; } // Add character to line parser->lineBuffer[lineLen++] = ch; } if (code == TSDB_CODE_SUCCESS) { parser->lineBuffer[lineLen] = '\0'; } return code; } static int32_t parseCsvFile(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, SRowsDataContext rowsDataCxt, int32_t* pNumOfRows) { int32_t code = TSDB_CODE_SUCCESS; (*pNumOfRows) = 0; // Initialize or use existing CSV parser in pStmt if (pStmt->pCsvParser == NULL) { // First time - allocate and initialize CSV parser pStmt->pCsvParser = taosMemoryMalloc(sizeof(SCsvParser)); if (!pStmt->pCsvParser) { return terrno; } code = csvParserInit(pStmt->pCsvParser, pStmt->fp); if (code != TSDB_CODE_SUCCESS) { taosMemoryFree(pStmt->pCsvParser); pStmt->pCsvParser = NULL; return code; } } // If pStmt->pCsvParser exists, we continue from where we left off bool firstLine = (pStmt->fileProcessing == false); pStmt->fileProcessing = false; while (TSDB_CODE_SUCCESS == code) { // Read one line from CSV using the parser in pStmt code = csvParserReadLine(pStmt->pCsvParser); if (code == TSDB_CODE_TSC_QUERY_CANCELLED) { // End of file code = TSDB_CODE_SUCCESS; break; } if (code != TSDB_CODE_SUCCESS) { break; } // Skip empty lines if (!pStmt->pCsvParser->lineBuffer || strlen(pStmt->pCsvParser->lineBuffer) == 0) { firstLine = false; continue; } bool gotRow = false; SToken token; (void)strtolower(pStmt->pCsvParser->lineBuffer, pStmt->pCsvParser->lineBuffer); const char* pRow = pStmt->pCsvParser->lineBuffer; if (!pStmt->stbSyntax) { code = parseOneRow(pCxt, (const char**)&pRow, rowsDataCxt.pTableDataCxt, &gotRow, &token); } else { STableDataCxt* pTableDataCxt = NULL; code = parseOneStbRow(pCxt, pStmt, (const char**)&pRow, rowsDataCxt.pStbRowsCxt, &gotRow, &token, &pTableDataCxt); if (code == TSDB_CODE_SUCCESS) { SStbRowsDataContext* pStbRowsCxt = rowsDataCxt.pStbRowsCxt; void* pData = pTableDataCxt; code = taosHashPut(pStmt->pTableCxtHashObj, &pStbRowsCxt->pCtbMeta->uid, sizeof(pStbRowsCxt->pCtbMeta->uid), &pData, POINTER_BYTES); if (TSDB_CODE_SUCCESS != code) { break; } } } if (code && firstLine) { firstLine = false; code = 0; continue; } if (TSDB_CODE_SUCCESS == code && gotRow) { (*pNumOfRows)++; } if (TSDB_CODE_SUCCESS == code && (*pNumOfRows) >= tsMaxInsertBatchRows) { // Reached batch limit - keep the parser in pStmt for next batch pStmt->fileProcessing = true; break; } firstLine = false; } // Don't destroy the parser here - it will be cleaned up when file processing is complete parserDebug("QID:0x%" PRIx64 ", %d rows have been parsed", pCxt->pComCxt->requestId, *pNumOfRows); if (TSDB_CODE_SUCCESS == code && 0 == (*pNumOfRows) && 0 == pStmt->totalRowsNum && (!TSDB_QUERY_HAS_TYPE(pStmt->insertType, TSDB_QUERY_TYPE_STMT_INSERT)) && !pStmt->fileProcessing) { code = buildSyntaxErrMsg(&pCxt->msg, "no any data points", NULL); } return code; } static int32_t parseDataFromFileImpl(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, SRowsDataContext rowsDataCxt) { // init only for file if (NULL == pStmt->pTableCxtHashObj) { pStmt->pTableCxtHashObj = taosHashInit(128, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BINARY), true, HASH_NO_LOCK); if (!pStmt->pTableCxtHashObj) { return terrno; } } int32_t numOfRows = 0; int32_t code = parseCsvFile(pCxt, pStmt, rowsDataCxt, &numOfRows); if (TSDB_CODE_SUCCESS == code) { pStmt->totalRowsNum += numOfRows; pStmt->totalTbNum += 1; TSDB_QUERY_SET_TYPE(pStmt->insertType, TSDB_QUERY_TYPE_FILE_INSERT); if (rowsDataCxt.pTableDataCxt && rowsDataCxt.pTableDataCxt->pData) { rowsDataCxt.pTableDataCxt->pData->flags |= SUBMIT_REQ_FROM_FILE; } if (!pStmt->fileProcessing) { // File processing is complete, clean up saved CSV parser destroySavedCsvParser(pStmt); code = taosCloseFile(&pStmt->fp); if (TSDB_CODE_SUCCESS != code) { parserWarn("QID:0x%" PRIx64 ", failed to close file.", pCxt->pComCxt->requestId); } } else { parserDebug("QID:0x%" PRIx64 ", insert from csv. File is too large, do it in batches.", pCxt->pComCxt->requestId); } if (pStmt->insertType != TSDB_QUERY_TYPE_FILE_INSERT) { destroySavedCsvParser(pStmt); return buildSyntaxErrMsg(&pCxt->msg, "keyword VALUES or FILE is exclusive", NULL); } } else { // On error, also clean up saved CSV parser destroySavedCsvParser(pStmt); return buildInvalidOperationMsg(&pCxt->msg, tstrerror(code)); } // just record pTableCxt whose data come from file if (!pStmt->stbSyntax && numOfRows > 0) { void* pData = rowsDataCxt.pTableDataCxt; code = taosHashPut(pStmt->pTableCxtHashObj, &pStmt->pTableMeta->uid, sizeof(pStmt->pTableMeta->uid), &pData, POINTER_BYTES); } return code; } static int32_t parseDataFromFile(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, SToken* pFilePath, SRowsDataContext rowsDataCxt) { char filePathStr[PATH_MAX + 16] = {0}; if (TK_NK_STRING == pFilePath->type) { (void)trimString(pFilePath->z, pFilePath->n, filePathStr, sizeof(filePathStr)); if (strlen(filePathStr) >= PATH_MAX) { return buildSyntaxErrMsg(&pCxt->msg, "file path is too long, max length is 4096", pFilePath->z); } } else { if (pFilePath->n >= PATH_MAX) { return buildSyntaxErrMsg(&pCxt->msg, "file path is too long, max length is 4096", pFilePath->z); } strncpy(filePathStr, pFilePath->z, pFilePath->n); } pStmt->fp = taosOpenFile(filePathStr, TD_FILE_READ); if (NULL == pStmt->fp) { return terrno; } return parseDataFromFileImpl(pCxt, pStmt, rowsDataCxt); } static int32_t parseFileClause(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, SRowsDataContext rowsDataCxt, SToken* pToken) { if (tsUseAdapter) { return buildInvalidOperationMsg(&pCxt->msg, "proxy mode does not support csv loading"); } NEXT_TOKEN(pStmt->pSql, *pToken); if (0 == pToken->n || (TK_NK_STRING != pToken->type && TK_NK_ID != pToken->type)) { return buildSyntaxErrMsg(&pCxt->msg, "file path is required following keyword FILE", pToken->z); } return parseDataFromFile(pCxt, pStmt, pToken, rowsDataCxt); } // VALUES (field1_value, ...) [(field1_value2, ...) ...] | FILE csv_file_path static int32_t parseDataClause(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, SRowsDataContext rowsDataCxt) { SToken token; NEXT_TOKEN(pStmt->pSql, token); switch (token.type) { case TK_VALUES: if (TSDB_QUERY_HAS_TYPE(pStmt->insertType, TSDB_QUERY_TYPE_FILE_INSERT)) { return buildSyntaxErrMsg(&pCxt->msg, "keyword VALUES or FILE is exclusive", token.z); } return parseValuesClause(pCxt, pStmt, rowsDataCxt, &token); case TK_FILE: return parseFileClause(pCxt, pStmt, rowsDataCxt, &token); default: break; } return buildSyntaxErrMsg(&pCxt->msg, "keyword VALUES or FILE is expected", token.z); } static void destroyStbRowsDataContext(SStbRowsDataContext* pStbRowsCxt) { if (pStbRowsCxt == NULL) return; clearStbRowsDataContext(pStbRowsCxt); taosArrayDestroy(pStbRowsCxt->aColVals); pStbRowsCxt->aColVals = NULL; taosArrayDestroy(pStbRowsCxt->aTagVals); pStbRowsCxt->aTagVals = NULL; taosArrayDestroy(pStbRowsCxt->aTagNames); pStbRowsCxt->aTagNames = NULL; insDestroyBoundColInfo(&pStbRowsCxt->boundColsInfo); tTagFree(pStbRowsCxt->pTag); pStbRowsCxt->pTag = NULL; taosMemoryFreeClear(pStbRowsCxt->pCtbMeta); tdDestroySVCreateTbReq(pStbRowsCxt->pCreateCtbReq); taosMemoryFreeClear(pStbRowsCxt->pCreateCtbReq); } static int32_t constructStbRowsDataContext(SVnodeModifyOpStmt* pStmt, SStbRowsDataContext** ppStbRowsCxt) { SStbRowsDataContext* pStbRowsCxt = taosMemoryCalloc(1, sizeof(SStbRowsDataContext)); if (!pStbRowsCxt) { return terrno; } tNameAssign(&pStbRowsCxt->stbName, &pStmt->targetTableName); int32_t code = collectUseTable(&pStbRowsCxt->stbName, pStmt->pTableNameHashObj); if (TSDB_CODE_SUCCESS == code) { code = collectUseDatabase(&pStbRowsCxt->stbName, pStmt->pDbFNameHashObj); } if (TSDB_CODE_SUCCESS == code) { pStbRowsCxt->ctbName.type = TSDB_TABLE_NAME_T; pStbRowsCxt->ctbName.acctId = pStbRowsCxt->stbName.acctId; memcpy(pStbRowsCxt->ctbName.dbname, pStbRowsCxt->stbName.dbname, sizeof(pStbRowsCxt->stbName.dbname)); pStbRowsCxt->pTagCond = pStmt->pTagCond; pStbRowsCxt->pStbMeta = pStmt->pTableMeta; code = cloneTableMeta(pStbRowsCxt->pStbMeta, &pStbRowsCxt->pCtbMeta); } if (TSDB_CODE_SUCCESS == code) { pStbRowsCxt->pCtbMeta->tableType = TSDB_CHILD_TABLE; pStbRowsCxt->pCtbMeta->suid = pStbRowsCxt->pStbMeta->uid; pStbRowsCxt->aTagNames = taosArrayInit(8, TSDB_COL_NAME_LEN); if (!pStbRowsCxt->aTagNames) { code = terrno; } } if (TSDB_CODE_SUCCESS == code) { pStbRowsCxt->aTagVals = taosArrayInit(8, sizeof(STagVal)); if (!pStbRowsCxt->aTagVals) { code = terrno; } } if (TSDB_CODE_SUCCESS == code) { // col values and bound cols info of STableDataContext is not used pStbRowsCxt->aColVals = taosArrayInit(getNumOfColumns(pStbRowsCxt->pStbMeta), sizeof(SColVal)); if (!pStbRowsCxt->aColVals) code = terrno; } if (TSDB_CODE_SUCCESS == code) { code = insInitColValues(pStbRowsCxt->pStbMeta, pStbRowsCxt->aColVals); } if (TSDB_CODE_SUCCESS == code) { STableComInfo tblInfo = getTableInfo(pStmt->pTableMeta); code = insInitBoundColsInfo(tblInfo.numOfColumns + tblInfo.numOfTags + 1, &pStbRowsCxt->boundColsInfo); } if (TSDB_CODE_SUCCESS == code) { *ppStbRowsCxt = pStbRowsCxt; } else { clearStbRowsDataContext(pStbRowsCxt); } return code; } static int32_t parseInsertStbClauseBottom(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) { int32_t code = TSDB_CODE_SUCCESS; if (!pStmt->pBoundCols) { return buildSyntaxErrMsg(&pCxt->msg, "(...tbname, ts...) bounded cols is expected for supertable insertion", pStmt->pSql); } SStbRowsDataContext* pStbRowsCxt = NULL; code = constructStbRowsDataContext(pStmt, &pStbRowsCxt); if (code == TSDB_CODE_SUCCESS) { code = parseBoundColumns(pCxt, &pStmt->pBoundCols, BOUND_ALL_AND_TBNAME, pStmt->pTableMeta, &pStbRowsCxt->boundColsInfo); pStbRowsCxt->hasTimestampTag = false; for (int32_t i = 0; i < pStbRowsCxt->boundColsInfo.numOfBound; ++i) { int16_t schemaIndex = pStbRowsCxt->boundColsInfo.pColIndex[i]; if (schemaIndex != getTbnameSchemaIndex(pStmt->pTableMeta) && schemaIndex >= getNumOfColumns(pStmt->pTableMeta)) { if (pStmt->pTableMeta->schema[schemaIndex].type == TSDB_DATA_TYPE_TIMESTAMP) { pStbRowsCxt->hasTimestampTag = true; } if (pStmt->pTableMeta->schema[schemaIndex].type == TSDB_DATA_TYPE_JSON) { pStbRowsCxt->isJsonTag = true; } } } pStmt->pStbRowsCxt = pStbRowsCxt; } if (code == TSDB_CODE_SUCCESS) { SRowsDataContext rowsDataCxt; rowsDataCxt.pStbRowsCxt = pStbRowsCxt; code = parseDataClause(pCxt, pStmt, rowsDataCxt); } return code; } // input pStmt->pSql: // 1. [(tag1_name, ...)] ... // 2. VALUES ... | FILE ... static int32_t parseInsertTableClauseBottom(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) { if (!pStmt->stbSyntax) { STableDataCxt* pTableCxt = NULL; int32_t code = parseSchemaClauseBottom(pCxt, pStmt, &pTableCxt); SRowsDataContext rowsDataCxt; rowsDataCxt.pTableDataCxt = pTableCxt; if (TSDB_CODE_SUCCESS == code) { code = parseDataClause(pCxt, pStmt, rowsDataCxt); } return code; } else { int32_t code = parseInsertStbClauseBottom(pCxt, pStmt); return code; } } static void resetEnvPreTable(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) { insDestroyBoundColInfo(&pCxt->tags); taosMemoryFreeClear(pStmt->pTableMeta); nodesDestroyNode(pStmt->pTagCond); taosArrayDestroy(pStmt->pTableTag); tdDestroySVCreateTbReq(pStmt->pCreateTblReq); taosMemoryFreeClear(pStmt->pCreateTblReq); pCxt->missCache = false; pCxt->usingDuplicateTable = false; pStmt->pBoundCols = NULL; pStmt->usingTableProcessing = false; pStmt->fileProcessing = false; pStmt->usingTableName.type = 0; destroyStbRowsDataContext(pStmt->pStbRowsCxt); taosMemoryFreeClear(pStmt->pStbRowsCxt); pStmt->stbSyntax = false; } // input pStmt->pSql: [(field1_name, ...)] [ USING ... ] VALUES ... | FILE ... static int32_t parseInsertTableClause(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, SToken* pTbName) { resetEnvPreTable(pCxt, pStmt); int32_t code = parseSchemaClauseTop(pCxt, pStmt, pTbName); if (TSDB_CODE_SUCCESS == code && !pCxt->missCache) { code = parseInsertTableClauseBottom(pCxt, pStmt); } return code; } static int32_t checkTableClauseFirstToken(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, SToken* pTbName, bool* pHasData) { // no data in the sql string anymore. if (0 == pTbName->n) { if (0 != pTbName->type && '\0' != pStmt->pSql[0]) { return buildSyntaxErrMsg(&pCxt->msg, "invalid table name", pTbName->z); } if (0 == pStmt->totalRowsNum && (!TSDB_QUERY_HAS_TYPE(pStmt->insertType, TSDB_QUERY_TYPE_STMT_INSERT))) { return buildInvalidOperationMsg(&pCxt->msg, "no data in sql"); } *pHasData = false; return TSDB_CODE_SUCCESS; } if (TSDB_QUERY_HAS_TYPE(pStmt->insertType, TSDB_QUERY_TYPE_STMT_INSERT) && pStmt->totalTbNum > 0) { return buildInvalidOperationMsg(&pCxt->msg, "single table allowed in one stmt"); } if (TK_NK_QUESTION == pTbName->type) { pCxt->stmtTbNameFlag &= ~IS_FIXED_VALUE; pCxt->isStmtBind = true; if (NULL == pCxt->pComCxt->pStmtCb) { return buildSyntaxErrMsg(&pCxt->msg, "? only used in stmt", pTbName->z); } char* tbName = NULL; int32_t code = (*pCxt->pComCxt->pStmtCb->getTbNameFn)(pCxt->pComCxt->pStmtCb->pStmt, &tbName); if (TSDB_CODE_SUCCESS == code) { pCxt->stmtTbNameFlag |= HAS_BIND_VALUE; pTbName->z = tbName; pTbName->n = strlen(tbName); } if (code == TSDB_CODE_TSC_STMT_TBNAME_ERROR) { pCxt->stmtTbNameFlag &= ~HAS_BIND_VALUE; code = TSDB_CODE_SUCCESS; } return code; } if (TK_NK_ID != pTbName->type && TK_NK_STRING != pTbName->type && TK_NK_QUESTION != pTbName->type) { return buildSyntaxErrMsg(&pCxt->msg, "table_name is expected", pTbName->z); } // db.? situation,ensure that the only thing following the '.' mark is '?' char* tbNameAfterDbName = strnchr(pTbName->z, '.', pTbName->n, true); if (tbNameAfterDbName != NULL) { if (*(tbNameAfterDbName + 1) == '?') { pCxt->stmtTbNameFlag &= ~IS_FIXED_VALUE; char* tbName = NULL; if (NULL == pCxt->pComCxt->pStmtCb) { return buildSyntaxErrMsg(&pCxt->msg, "? only used in stmt", pTbName->z); } int32_t code = (*pCxt->pComCxt->pStmtCb->getTbNameFn)(pCxt->pComCxt->pStmtCb->pStmt, &tbName); if (TSDB_CODE_SUCCESS == code) { pCxt->stmtTbNameFlag |= HAS_BIND_VALUE; pTbName->z = tbName; pTbName->n = strlen(tbName); } if (code == TSDB_CODE_TSC_STMT_TBNAME_ERROR) { pCxt->stmtTbNameFlag &= ~HAS_BIND_VALUE; code = TSDB_CODE_SUCCESS; } } else { pCxt->stmtTbNameFlag |= IS_FIXED_VALUE; parserWarn("QID:0x%" PRIx64 ", table name is specified in sql, ignore the table name in bind param", pCxt->pComCxt->requestId); *pHasData = true; } return TSDB_CODE_SUCCESS; } if (TK_NK_ID == pTbName->type) { pCxt->stmtTbNameFlag |= IS_FIXED_VALUE; } *pHasData = true; return TSDB_CODE_SUCCESS; } static int32_t setStmtInfo(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) { SBoundColInfo* tags = taosMemoryMalloc(sizeof(pCxt->tags)); if (NULL == tags) { return terrno; } memcpy(tags, &pCxt->tags, sizeof(pCxt->tags)); SStmtCallback* pStmtCb = pCxt->pComCxt->pStmtCb; int32_t code = (*pStmtCb->setInfoFn)(pStmtCb->pStmt, pStmt->pTableMeta, tags, &pStmt->targetTableName, pStmt->usingTableProcessing, pStmt->pVgroupsHashObj, pStmt->pTableBlockHashObj, pStmt->usingTableName.tname, pCxt->stmtTbNameFlag); memset(&pCxt->tags, 0, sizeof(pCxt->tags)); pStmt->pVgroupsHashObj = NULL; pStmt->pTableBlockHashObj = NULL; return code; } static int32_t parseInsertBodyBottom(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) { if (TSDB_QUERY_HAS_TYPE(pStmt->insertType, TSDB_QUERY_TYPE_STMT_INSERT)) { return setStmtInfo(pCxt, pStmt); } // release old array alloced by merge pStmt->freeArrayFunc(pStmt->pVgDataBlocks); pStmt->pVgDataBlocks = NULL; bool fileOnly = (pStmt->insertType == TSDB_QUERY_TYPE_FILE_INSERT); if (fileOnly) { // none data, skip merge & buildvgdata if (0 == taosHashGetSize(pStmt->pTableCxtHashObj)) { pCxt->needRequest = false; return TSDB_CODE_SUCCESS; } } // merge according to vgId int32_t code = insMergeTableDataCxt(fileOnly ? pStmt->pTableCxtHashObj : pStmt->pTableBlockHashObj, &pStmt->pVgDataBlocks, pStmt->fileProcessing); // clear tmp hashobj only taosHashClear(pStmt->pTableCxtHashObj); if (TSDB_CODE_SUCCESS == code) { code = insBuildVgDataBlocks(pStmt->pVgroupsHashObj, pStmt->pVgDataBlocks, &pStmt->pDataBlocks, false); } return code; } // tb_name // [USING stb_name [(tag1_name, ...)] TAGS (tag1_value, ...)] // [(field1_name, ...)] // VALUES (field1_value, ...) [(field1_value2, ...) ...] | FILE csv_file_path // [...]; static int32_t parseInsertBody(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) { SToken token; int32_t code = TSDB_CODE_SUCCESS; bool hasData = true; // for each table while (TSDB_CODE_SUCCESS == code && hasData && !pCxt->missCache && !pStmt->fileProcessing) { // pStmt->pSql -> tb_name ... NEXT_TOKEN(pStmt->pSql, token); code = checkTableClauseFirstToken(pCxt, pStmt, &token, &hasData); if (TSDB_CODE_SUCCESS == code && hasData) { code = parseInsertTableClause(pCxt, pStmt, &token); } if( TSDB_CODE_SUCCESS == code && pStmt->pTableMeta && (((pStmt->pTableMeta->virtualStb == 1) && (pStmt->pTableMeta->tableType == TSDB_SUPER_TABLE)) || (pStmt->pTableMeta->tableType == TSDB_VIRTUAL_NORMAL_TABLE || pStmt->pTableMeta->tableType == TSDB_VIRTUAL_CHILD_TABLE))) { code = buildInvalidOperationMsg(&pCxt->msg, "Virtual table can not be written"); } } if (TSDB_CODE_SUCCESS == code && !pCxt->missCache) { code = parseInsertBodyBottom(pCxt, pStmt); } return code; } static void destroySubTableHashElem(void* p) { taosMemoryFree(*(STableMeta**)p); } static int32_t createVnodeModifOpStmt(SInsertParseContext* pCxt, bool reentry, SNode** pOutput) { SVnodeModifyOpStmt* pStmt = NULL; int32_t code = nodesMakeNode(QUERY_NODE_VNODE_MODIFY_STMT, (SNode**)&pStmt); if (NULL == pStmt) { return code; } if (pCxt->pComCxt->pStmtCb) { TSDB_QUERY_SET_TYPE(pStmt->insertType, TSDB_QUERY_TYPE_STMT_INSERT); } pStmt->pSql = pCxt->pComCxt->pSql; pStmt->freeHashFunc = insDestroyTableDataCxtHashMap; pStmt->freeArrayFunc = insDestroyVgroupDataCxtList; pStmt->freeStbRowsCxtFunc = destroyStbRowsDataContext; pStmt->pCsvParser = NULL; if (!reentry) { pStmt->pVgroupsHashObj = taosHashInit(128, taosGetDefaultHashFunction(TSDB_DATA_TYPE_INT), true, HASH_ENTRY_LOCK); if (pCxt->pComCxt->pStmtCb) { pStmt->pTableBlockHashObj = taosHashInit(128, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BINARY), true, HASH_NO_LOCK); } else { pStmt->pTableBlockHashObj = taosHashInit(128, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BIGINT), true, HASH_NO_LOCK); } } pStmt->pSubTableHashObj = taosHashInit(128, taosGetDefaultHashFunction(TSDB_DATA_TYPE_VARCHAR), true, HASH_NO_LOCK); pStmt->pSuperTableHashObj = taosHashInit(128, taosGetDefaultHashFunction(TSDB_DATA_TYPE_VARCHAR), true, HASH_NO_LOCK); pStmt->pTableNameHashObj = taosHashInit(128, taosGetDefaultHashFunction(TSDB_DATA_TYPE_VARCHAR), true, HASH_NO_LOCK); pStmt->pDbFNameHashObj = taosHashInit(64, taosGetDefaultHashFunction(TSDB_DATA_TYPE_VARCHAR), true, HASH_NO_LOCK); if ((!reentry && (NULL == pStmt->pVgroupsHashObj || NULL == pStmt->pTableBlockHashObj)) || NULL == pStmt->pSubTableHashObj || NULL == pStmt->pTableNameHashObj || NULL == pStmt->pDbFNameHashObj) { nodesDestroyNode((SNode*)pStmt); return TSDB_CODE_OUT_OF_MEMORY; } taosHashSetFreeFp(pStmt->pSubTableHashObj, destroySubTableHashElem); taosHashSetFreeFp(pStmt->pSuperTableHashObj, destroySubTableHashElem); *pOutput = (SNode*)pStmt; return TSDB_CODE_SUCCESS; } static int32_t createInsertQuery(SInsertParseContext* pCxt, SQuery** pOutput) { SQuery* pQuery = NULL; int32_t code = nodesMakeNode(QUERY_NODE_QUERY, (SNode**)&pQuery); if (NULL == pQuery) { return code; } pQuery->execMode = QUERY_EXEC_MODE_SCHEDULE; pQuery->haveResultSet = false; pQuery->msgType = TDMT_VND_SUBMIT; code = createVnodeModifOpStmt(pCxt, false, &pQuery->pRoot); if (TSDB_CODE_SUCCESS == code) { *pOutput = pQuery; } else { nodesDestroyNode((SNode*)pQuery); } return code; } static int32_t checkAuthFromMetaData(const SArray* pUsers, SNode** pTagCond) { if (1 != taosArrayGetSize(pUsers)) { return TSDB_CODE_FAILED; } SMetaRes* pRes = taosArrayGet(pUsers, 0); if (TSDB_CODE_SUCCESS == pRes->code) { SUserAuthRes* pAuth = pRes->pRes; pRes->code = nodesCloneNode(pAuth->pCond[AUTH_RES_BASIC], pTagCond); if (TSDB_CODE_SUCCESS == pRes->code) { return pAuth->pass[AUTH_RES_BASIC] ? TSDB_CODE_SUCCESS : TSDB_CODE_PAR_PERMISSION_DENIED; } } return pRes->code; } static int32_t getTableMetaFromMetaData(const SArray* pTables, STableMeta** pMeta) { if (1 != taosArrayGetSize(pTables) && 2 != taosArrayGetSize(pTables)) { return TSDB_CODE_FAILED; } taosMemoryFreeClear(*pMeta); SMetaRes* pRes = taosArrayGet(pTables, 0); if (TSDB_CODE_SUCCESS == pRes->code) { *pMeta = tableMetaDup((const STableMeta*)pRes->pRes); if (NULL == *pMeta) { return TSDB_CODE_OUT_OF_MEMORY; } } return pRes->code; } static int32_t addTableVgroupFromMetaData(const SArray* pTables, SVnodeModifyOpStmt* pStmt, bool isStb) { if (1 != taosArrayGetSize(pTables)) { return TSDB_CODE_FAILED; } SMetaRes* pRes = taosArrayGet(pTables, 0); if (TSDB_CODE_SUCCESS != pRes->code) { return pRes->code; } SVgroupInfo* pVg = pRes->pRes; if (isStb) { pStmt->pTableMeta->vgId = pVg->vgId; } return taosHashPut(pStmt->pVgroupsHashObj, (const char*)&pVg->vgId, sizeof(pVg->vgId), (char*)pVg, sizeof(SVgroupInfo)); } static int32_t buildTagNameFromMeta(STableMeta* pMeta, SArray** pTagName) { *pTagName = taosArrayInit(pMeta->tableInfo.numOfTags, TSDB_COL_NAME_LEN); if (NULL == *pTagName) { return terrno; } SSchema* pSchema = getTableTagSchema(pMeta); int32_t code = 0; for (int32_t i = 0; i < pMeta->tableInfo.numOfTags; ++i) { if (NULL == taosArrayPush(*pTagName, pSchema[i].name)) { code = terrno; taosArrayDestroy(*pTagName); *pTagName = NULL; break; } } return code; } static int32_t checkSubtablePrivilegeForTable(const SArray* pTables, SVnodeModifyOpStmt* pStmt) { if (1 != taosArrayGetSize(pTables)) { return TSDB_CODE_FAILED; } SMetaRes* pRes = taosArrayGet(pTables, 0); if (TSDB_CODE_SUCCESS != pRes->code) { return pRes->code; } SArray* pTagName = NULL; int32_t code = buildTagNameFromMeta(pStmt->pTableMeta, &pTagName); if (TSDB_CODE_SUCCESS == code) { code = checkSubtablePrivilege((SArray*)pRes->pRes, pTagName, &pStmt->pTagCond); } taosArrayDestroy(pTagName); return code; } static int32_t processTableSchemaFromMetaData(SInsertParseContext* pCxt, const SMetaData* pMetaData, SVnodeModifyOpStmt* pStmt, bool isStb) { int32_t code = TSDB_CODE_SUCCESS; if (!isStb && TSDB_SUPER_TABLE == pStmt->pTableMeta->tableType) { code = buildInvalidOperationMsg(&pCxt->msg, "insert data into super table is not supported"); } if (TSDB_CODE_SUCCESS == code && isStb) { code = storeChildTableMeta(pCxt, pStmt); } if (TSDB_CODE_SUCCESS == code) { code = addTableVgroupFromMetaData(pMetaData->pTableHash, pStmt, isStb); } if (TSDB_CODE_SUCCESS == code && !isStb && NULL != pStmt->pTagCond) { code = checkSubtablePrivilegeForTable(pMetaData->pTableTag, pStmt); } return code; } static void destoryTablesReq(void* p) { STablesReq* pRes = (STablesReq*)p; taosArrayDestroy(pRes->pTables); } static void clearCatalogReq(SCatalogReq* pCatalogReq) { if (NULL == pCatalogReq) { return; } taosArrayDestroyEx(pCatalogReq->pTableMeta, destoryTablesReq); pCatalogReq->pTableMeta = NULL; taosArrayDestroyEx(pCatalogReq->pTableHash, destoryTablesReq); pCatalogReq->pTableHash = NULL; taosArrayDestroy(pCatalogReq->pUser); pCatalogReq->pUser = NULL; taosArrayDestroy(pCatalogReq->pTableTag); pCatalogReq->pTableTag = NULL; } static int32_t setVnodeModifOpStmt(SInsertParseContext* pCxt, SCatalogReq* pCatalogReq, const SMetaData* pMetaData, SVnodeModifyOpStmt* pStmt) { clearCatalogReq(pCatalogReq); int32_t code = checkAuthFromMetaData(pMetaData->pUser, &pStmt->pTagCond); if (code == TSDB_CODE_SUCCESS) { code = getTableMetaFromMetaData(pMetaData->pTableMeta, &pStmt->pTableMeta); } if (code == TSDB_CODE_SUCCESS) { if (pStmt->pTableMeta->tableType == TSDB_SUPER_TABLE && !pStmt->usingTableProcessing) { pStmt->stbSyntax = true; } if (!pStmt->stbSyntax) { if (pStmt->usingTableProcessing) { return processTableSchemaFromMetaData(pCxt, pMetaData, pStmt, true); } return processTableSchemaFromMetaData(pCxt, pMetaData, pStmt, false); } } return code; } static int32_t resetVnodeModifOpStmt(SInsertParseContext* pCxt, SQuery* pQuery) { nodesDestroyNode(pQuery->pRoot); int32_t code = createVnodeModifOpStmt(pCxt, true, &pQuery->pRoot); if (TSDB_CODE_SUCCESS == code) { SVnodeModifyOpStmt* pStmt = (SVnodeModifyOpStmt*)pQuery->pRoot; code = (*pCxt->pComCxt->pStmtCb->getExecInfoFn)(pCxt->pComCxt->pStmtCb->pStmt, &pStmt->pVgroupsHashObj, &pStmt->pTableBlockHashObj); if (TSDB_CODE_SUCCESS == code) { if (NULL == pStmt->pVgroupsHashObj) { pStmt->pVgroupsHashObj = taosHashInit(128, taosGetDefaultHashFunction(TSDB_DATA_TYPE_INT), true, HASH_NO_LOCK); } if (NULL == pStmt->pTableBlockHashObj) { pStmt->pTableBlockHashObj = taosHashInit(128, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BINARY), true, HASH_NO_LOCK); } if (NULL == pStmt->pVgroupsHashObj || NULL == pStmt->pTableBlockHashObj) { code = TSDB_CODE_OUT_OF_MEMORY; } } } return code; } static int32_t initInsertQuery(SInsertParseContext* pCxt, SCatalogReq* pCatalogReq, const SMetaData* pMetaData, SQuery** pQuery) { if (NULL == *pQuery) { return createInsertQuery(pCxt, pQuery); } if (NULL != pCxt->pComCxt->pStmtCb) { return resetVnodeModifOpStmt(pCxt, *pQuery); } SVnodeModifyOpStmt* pStmt = (SVnodeModifyOpStmt*)(*pQuery)->pRoot; if (!pStmt->fileProcessing) { return setVnodeModifOpStmt(pCxt, pCatalogReq, pMetaData, pStmt); } return TSDB_CODE_SUCCESS; } static int32_t setRefreshMeta(SQuery* pQuery) { SVnodeModifyOpStmt* pStmt = (SVnodeModifyOpStmt*)pQuery->pRoot; int32_t code = 0; if (taosHashGetSize(pStmt->pTableNameHashObj) > 0) { taosArrayDestroy(pQuery->pTableList); pQuery->pTableList = taosArrayInit(taosHashGetSize(pStmt->pTableNameHashObj), sizeof(SName)); if (!pQuery->pTableList) { code = terrno; } else { SName* pTable = taosHashIterate(pStmt->pTableNameHashObj, NULL); while (NULL != pTable) { if (NULL == taosArrayPush(pQuery->pTableList, pTable)) { code = terrno; taosHashCancelIterate(pStmt->pTableNameHashObj, pTable); break; } pTable = taosHashIterate(pStmt->pTableNameHashObj, pTable); } } } if (TSDB_CODE_SUCCESS == code && taosHashGetSize(pStmt->pDbFNameHashObj) > 0) { taosArrayDestroy(pQuery->pDbList); pQuery->pDbList = taosArrayInit(taosHashGetSize(pStmt->pDbFNameHashObj), TSDB_DB_FNAME_LEN); if (!pQuery->pDbList) { code = terrno; } else { char* pDb = taosHashIterate(pStmt->pDbFNameHashObj, NULL); while (NULL != pDb) { if (NULL == taosArrayPush(pQuery->pDbList, pDb)) { code = terrno; taosHashCancelIterate(pStmt->pDbFNameHashObj, pDb); break; } pDb = taosHashIterate(pStmt->pDbFNameHashObj, pDb); } } } return code; } // INSERT INTO // tb_name // [USING stb_name [(tag1_name, ...)] TAGS (tag1_value, ...) [table_options]] // [(field1_name, ...)] // VALUES (field1_value, ...) [(field1_value2, ...) ...] | FILE csv_file_path // [...]; static int32_t parseInsertSqlFromStart(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) { int32_t code = skipInsertInto(&pStmt->pSql, &pCxt->msg); if (TSDB_CODE_SUCCESS == code) { code = parseInsertBody(pCxt, pStmt); } return code; } static int32_t parseInsertSqlFromCsv(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) { int32_t code = TSDB_CODE_SUCCESS; SRowsDataContext rowsDataCxt; if (!pStmt->stbSyntax) { STableDataCxt* pTableCxt = NULL; code = getTableDataCxt(pCxt, pStmt, &pTableCxt); rowsDataCxt.pTableDataCxt = pTableCxt; } else { rowsDataCxt.pStbRowsCxt = pStmt->pStbRowsCxt; } if (TSDB_CODE_SUCCESS == code) { code = parseDataFromFileImpl(pCxt, pStmt, rowsDataCxt); } if (TSDB_CODE_SUCCESS == code) { if (pStmt->fileProcessing) { code = parseInsertBodyBottom(pCxt, pStmt); } else { code = parseInsertBody(pCxt, pStmt); } } return code; } static int32_t parseInsertSqlFromTable(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) { int32_t code = parseInsertTableClauseBottom(pCxt, pStmt); if (TSDB_CODE_SUCCESS == code) { code = parseInsertBody(pCxt, pStmt); } return code; } static int32_t parseInsertSqlImpl(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt) { if (pStmt->pSql == pCxt->pComCxt->pSql || NULL != pCxt->pComCxt->pStmtCb) { return parseInsertSqlFromStart(pCxt, pStmt); } if (pStmt->fileProcessing) { return parseInsertSqlFromCsv(pCxt, pStmt); } return parseInsertSqlFromTable(pCxt, pStmt); } static int32_t buildUsingInsertTableReq(SName* pSName, SName* pCName, SArray** pTables) { if (NULL == *pTables) { *pTables = taosArrayInit(2, sizeof(SName)); if (NULL == *pTables) { goto _err; } } if (NULL == taosArrayPush(*pTables, pSName)) { goto _err; } if (NULL == taosArrayPush(*pTables, pCName)) { goto _err; } return TSDB_CODE_SUCCESS; _err: if (NULL != *pTables) { taosArrayDestroy(*pTables); *pTables = NULL; } return terrno; } static int32_t buildInsertTableReq(SName* pName, SArray** pTables) { *pTables = taosArrayInit(1, sizeof(SName)); if (NULL == *pTables) { return terrno; } if (NULL == taosArrayPush(*pTables, pName)) { taosArrayDestroy(*pTables); *pTables = NULL; return terrno; } return TSDB_CODE_SUCCESS; } static int32_t buildInsertUsingDbReq(SName* pSName, SName* pCName, SArray** pDbs) { if (NULL == *pDbs) { *pDbs = taosArrayInit(1, sizeof(STablesReq)); if (NULL == *pDbs) { return terrno; } } STablesReq req = {0}; req.autoCreate = 1; (void)tNameGetFullDbName(pSName, req.dbFName); (void)tNameGetFullDbName(pCName, req.dbFName); int32_t code = buildUsingInsertTableReq(pSName, pCName, &req.pTables); if (TSDB_CODE_SUCCESS == code && NULL == taosArrayPush(*pDbs, &req)) { code = TSDB_CODE_OUT_OF_MEMORY; } return code; } static int32_t buildInsertDbReq(SName* pName, SArray** pDbs) { if (NULL == *pDbs) { *pDbs = taosArrayInit(1, sizeof(STablesReq)); if (NULL == *pDbs) { return terrno; } } STablesReq req = {0}; (void)tNameGetFullDbName(pName, req.dbFName); int32_t code = buildInsertTableReq(pName, &req.pTables); if (TSDB_CODE_SUCCESS == code && NULL == taosArrayPush(*pDbs, &req)) { code = TSDB_CODE_OUT_OF_MEMORY; } return code; } static int32_t buildInsertUserAuthReq(const char* pUser, SName* pName, SArray** pUserAuth) { *pUserAuth = taosArrayInit(1, sizeof(SUserAuthInfo)); if (NULL == *pUserAuth) { return terrno; } SUserAuthInfo userAuth = {.type = AUTH_TYPE_WRITE}; snprintf(userAuth.user, sizeof(userAuth.user), "%s", pUser); memcpy(&userAuth.tbName, pName, sizeof(SName)); if (NULL == taosArrayPush(*pUserAuth, &userAuth)) { taosArrayDestroy(*pUserAuth); *pUserAuth = NULL; return terrno; } return TSDB_CODE_SUCCESS; } static int32_t buildInsertTableTagReq(SName* pName, SArray** pTables) { return buildInsertTableReq(pName, pTables); } static int32_t buildInsertCatalogReq(SInsertParseContext* pCxt, SVnodeModifyOpStmt* pStmt, SCatalogReq* pCatalogReq) { int32_t code = buildInsertUserAuthReq( pCxt->pComCxt->pUser, (0 == pStmt->usingTableName.type ? &pStmt->targetTableName : &pStmt->usingTableName), &pCatalogReq->pUser); if (TSDB_CODE_SUCCESS == code && pCxt->needTableTagVal) { code = buildInsertTableTagReq(&pStmt->targetTableName, &pCatalogReq->pTableTag); } if (TSDB_CODE_SUCCESS == code) { if (0 == pStmt->usingTableName.type) { code = buildInsertDbReq(&pStmt->targetTableName, &pCatalogReq->pTableMeta); } else { code = buildInsertUsingDbReq(&pStmt->usingTableName, &pStmt->targetTableName, &pCatalogReq->pTableMeta); } } if (TSDB_CODE_SUCCESS == code) { code = buildInsertDbReq(&pStmt->targetTableName, &pCatalogReq->pTableHash); } return code; } static int32_t setNextStageInfo(SInsertParseContext* pCxt, SQuery* pQuery, SCatalogReq* pCatalogReq) { SVnodeModifyOpStmt* pStmt = (SVnodeModifyOpStmt*)pQuery->pRoot; if (pCxt->missCache) { parserDebug("QID:0x%" PRIx64 ", %d rows of %d tables will be inserted before obtain the cache", pCxt->pComCxt->requestId, pStmt->totalRowsNum, pStmt->totalTbNum); pQuery->execStage = QUERY_EXEC_STAGE_PARSE; return buildInsertCatalogReq(pCxt, pStmt, pCatalogReq); } parserDebug("QID:0x%" PRIx64 ", %d rows of %d tables will be inserted", pCxt->pComCxt->requestId, pStmt->totalRowsNum, pStmt->totalTbNum); pQuery->execStage = QUERY_EXEC_STAGE_SCHEDULE; return TSDB_CODE_SUCCESS; } int32_t parseInsertSql(SParseContext* pCxt, SQuery** pQuery, SCatalogReq* pCatalogReq, const SMetaData* pMetaData) { SInsertParseContext context = {.pComCxt = pCxt, .msg = {.buf = pCxt->pMsg, .len = pCxt->msgLen}, .missCache = false, .usingDuplicateTable = false, .needRequest = true, .forceUpdate = (NULL != pCatalogReq ? pCatalogReq->forceUpdate : false), .isStmtBind = pCxt->isStmtBind}; int32_t code = initInsertQuery(&context, pCatalogReq, pMetaData, pQuery); if (TSDB_CODE_SUCCESS == code) { code = parseInsertSqlImpl(&context, (SVnodeModifyOpStmt*)((*pQuery)->pRoot)); } if (TSDB_CODE_SUCCESS == code) { code = setNextStageInfo(&context, *pQuery, pCatalogReq); } if ((TSDB_CODE_SUCCESS == code || NEED_CLIENT_HANDLE_ERROR(code)) && QUERY_EXEC_STAGE_SCHEDULE == (*pQuery)->execStage) { code = setRefreshMeta(*pQuery); } insDestroyBoundColInfo(&context.tags); // if no data to insert, set emptyMode to avoid request server if (!context.needRequest) { (*pQuery)->execMode = QUERY_EXEC_MODE_EMPTY_RESULT; } return code; } // CSV Parser Implementation static int32_t csvParserInit(SCsvParser* parser, TdFilePtr pFile) { if (!parser || !pFile) { return TSDB_CODE_INVALID_PARA; } memset(parser, 0, sizeof(SCsvParser)); // Set default CSV format parser->delimiter = CSV_DEFAULT_DELIMITER; parser->quote = CSV_QUOTE_SINGLE; // Default to single quote for TDengine compatibility parser->escape = CSV_ESCAPE_CHAR; parser->allowNewlineInField = true; // Initialize buffer parser->bufferSize = 64 * 1024; // 64KB buffer parser->buffer = taosMemoryMalloc(parser->bufferSize); if (!parser->buffer) { return terrno; } // Initialize line buffer for reuse parser->lineBufferCapacity = 64 * 1024; // Initial 64KB line buffer parser->lineBuffer = taosMemoryMalloc(parser->lineBufferCapacity); if (!parser->lineBuffer) { return terrno; } parser->bufferPos = 0; parser->bufferLen = 0; parser->eof = false; parser->pFile = pFile; // Fill initial buffer to detect quote type int32_t code = csvParserFillBuffer(parser); if (code != TSDB_CODE_SUCCESS) { return code; } // Auto-detect quote character by finding the first quote in the file // Skip the header line and look for the first quote character in data bool foundFirstQuote = false; bool inFirstLine = true; for (size_t i = 0; i < parser->bufferLen && !foundFirstQuote; i++) { char ch = parser->buffer[i]; // Skip the first line (header) if (inFirstLine) { if (ch == '\n') { inFirstLine = false; } continue; } // Look for the first quote character in data lines if (ch == CSV_QUOTE_SINGLE) { parser->quote = CSV_QUOTE_SINGLE; foundFirstQuote = true; } else if (ch == CSV_QUOTE_DOUBLE) { parser->quote = CSV_QUOTE_DOUBLE; foundFirstQuote = true; } } // If no quotes found, keep default (single quote for TDengine compatibility) // Reset buffer position for actual parsing parser->bufferPos = 0; return TSDB_CODE_SUCCESS; } static void csvParserDestroy(SCsvParser* parser) { if (parser) { taosMemoryFree(parser->buffer); taosMemoryFree(parser->lineBuffer); memset(parser, 0, sizeof(SCsvParser)); } } static int32_t csvParserFillBuffer(SCsvParser* parser) { if (!parser || parser->eof) { return TSDB_CODE_SUCCESS; } // Move remaining data to beginning of buffer // Since this function is only called when bufferPos >= bufferLen, // we can simplify by always resetting the buffer parser->bufferLen = 0; parser->bufferPos = 0; // Read more data size_t spaceLeft = parser->bufferSize - parser->bufferLen; if (spaceLeft > 0) { int64_t bytesRead = taosReadFile(parser->pFile, parser->buffer + parser->bufferLen, spaceLeft); if (bytesRead < 0) { return TAOS_SYSTEM_ERROR(errno); } if (bytesRead == 0) { parser->eof = true; } else { parser->bufferLen += bytesRead; } } return TSDB_CODE_SUCCESS; } // Destroy saved CSV parser in SVnodeModifyOpStmt static void destroySavedCsvParser(SVnodeModifyOpStmt* pStmt) { if (pStmt && pStmt->pCsvParser) { csvParserDestroy(pStmt->pCsvParser); taosMemoryFree(pStmt->pCsvParser); pStmt->pCsvParser = NULL; } } static int32_t csvParserExpandLineBuffer(SCsvParser* parser, size_t requiredLen) { if (!parser || requiredLen <= parser->lineBufferCapacity) { return TSDB_CODE_SUCCESS; } size_t newCapacity = parser->lineBufferCapacity; while (newCapacity < requiredLen) { newCapacity *= 2; } char* newLineBuffer = taosMemoryRealloc(parser->lineBuffer, newCapacity); if (!newLineBuffer) { return TSDB_CODE_OUT_OF_MEMORY; } parser->lineBuffer = newLineBuffer; parser->lineBufferCapacity = newCapacity; return TSDB_CODE_SUCCESS; }