/* * 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 . */ #define __USE_XOPEN #include "os.h" #include "tglobal.h" #include "shell.h" #include "shellCommand.h" #include "tkey.h" #include "tulog.h" #include "shellAuto.h" #include "tire.h" #include "tthread.h" // // ------------- define area --------------- // #define UNION_ALL " union all " // extern function void insertChar(Command *cmd, char *c, int size); typedef struct SAutoPtr { STire* p; int ref; }SAutoPtr; typedef struct SWord{ int type ; // word type , see WT_ define char * word; int32_t len; struct SWord * next; bool free; // if true need free }SWord; typedef struct { char * source; int32_t source_len; // valid data length in source int32_t count; SWord* head; // matched information int32_t matchIndex; // matched word index in words int32_t matchLen; // matched length at matched word }SWords; SWords shellCommands[] = { {"alter database ", 0, 0, NULL}, {"alter dnode balance ", 0, 0, NULL}, {"alter dnode resetlog;", 0, 0, NULL}, {"alter dnode debugFlag 141;", 0, 0, NULL}, {"alter dnode monitor 1;", 0, 0, NULL}, {"alter table ", 0, 0, NULL}, {"alter table modify column", 0, 0, NULL}, {"alter local resetlog;", 0, 0, NULL}, {"alter local DebugFlag 143;", 0, 0, NULL}, {"alter local cDebugFlag 143;", 0, 0, NULL}, {"alter local uDebugFlag 143;", 0, 0, NULL}, {"alter local rpcDebugFlag 143;", 0, 0, NULL}, {"alter local tmrDebugFlag 143;", 0, 0, NULL}, {"alter topic", 0, 0, NULL}, {"alter user pass", 0, 0, NULL}, {"alter user tags", 0, 0, NULL}, {"alter user privilege read", 0, 0, NULL}, {"alter user privilege write", 0, 0, NULL}, {"create table using tags(", 0, 0, NULL}, {"create database ", 0, 0, NULL}, {"create table as ", 0, 0, NULL}, {"create dnode ", 0, 0, NULL}, {"create topic", 0, 0, NULL}, {"create function ", 0, 0, NULL}, {"create user pass tags", 0, 0, NULL}, {"compact vnode in", 0, 0, NULL}, {"describe ", 0, 0, NULL}, #ifdef TD_ENTERPRISE {"delete from where", 0, 0, NULL}, #endif {"drop database ", 0, 0, NULL}, {"drop table ", 0, 0, NULL}, {"drop dnode ", 0, 0, NULL}, {"drop user ;", 0, 0, NULL}, {"drop function", 0, 0, NULL}, {"drop topic", 0, 0, NULL}, {"kill connection", 0, 0, NULL}, {"kill query", 0, 0, NULL}, {"kill stream", 0, 0, NULL}, {"select * from where ", 0, 0, NULL}, {"select _block_dist() from \\G;", 0, 0, NULL}, {"select client_version();", 0, 0, NULL}, {"select current_user();", 0, 0, NULL}, {"select database;", 0, 0, NULL}, {"select server_version();", 0, 0, NULL}, {"set max_binary_display_width ", 0, 0, NULL}, {"show create database \\G;", 0, 0, NULL}, {"show create stable \\G;", 0, 0, NULL}, {"show create table \\G;", 0, 0, NULL}, {"show connections;", 0, 0, NULL}, {"show databases;", 0, 0, NULL}, {"show dnodes;", 0, 0, NULL}, {"show functions;", 0, 0, NULL}, {"show modules;", 0, 0, NULL}, {"show mnodes;", 0, 0, NULL}, {"show queries;", 0, 0, NULL}, {"show stables;", 0, 0, NULL}, {"show stables like ", 0, 0, NULL}, {"show streams;", 0, 0, NULL}, {"show scores;", 0, 0, NULL}, {"show tables;", 0, 0, NULL}, {"show tables like", 0, 0, NULL}, {"show users;", 0, 0, NULL}, {"show variables;", 0, 0, NULL}, {"show vgroups;", 0, 0, NULL}, {"insert into values(", 0, 0, NULL}, {"insert into using tags( ) values(", 0, 0, NULL}, {"use ", 0, 0, NULL}, {"quit", 0, 0, NULL} }; char * keywords[] = { "and ", "asc ", "desc ", "from ", "fill(", "limit ", "where ", "interval(", "order by ", "order by ", "offset ", "or ", "group by ", "now()", "session(", "sliding ", "slimit ", "soffset ", "state_window(", "today() ", "union all select ", }; char * functions[] = { "count(", "sum(", "avg(", "last(", "last_row(", "top(", "interp(", "max(", "min(", "now()", "today()", "percentile(", "tail(", "pow(", "abs(", "atan(", "acos(", "asin(", "apercentile(", "bottom(", "cast(", "ceil(", "char_length(", "cos(", "concat(", "concat_ws(", "csum(", "diff(", "derivative(", "elapsed(", "first(", "floor(", "hyperloglog(", "histogram(", "irate(", "leastsquares(", "length(", "log(", "lower(", "ltrim(", "mavg(", "mode(", "tan(", "round(", "rtrim(", "sample(", "sin(", "spread(", "substr(", "statecount(", "stateduration(", "stddev(", "sqrt(", "timediff(", "timezone(", "timetruncate(", "twa(", "to_unixtimestamp(", "unique(", "upper(", }; char * tb_actions[] = { "add column", "modify column", "drop column", "change tag", }; char * db_options[] = { "blocks", "cachelast", "comp", "keep", "replica", "quorum", }; char * data_types[] = { "timestamp", "int", "float", "double", "binary(16)", "nchar(16)", "bigint", "smallint", "tinyint", "bool", "json" }; char * key_tags[] = { "tags(" }; // // ------- gobal variant define --------- // int32_t firstMatchIndex = -1; // first match shellCommands index int32_t lastMatchIndex = -1; // last match shellCommands index int32_t curMatchIndex = -1; // current match shellCommands index int32_t lastWordBytes = -1; // printShow last word length bool waitAutoFill = false; // // ----------- global var array define ----------- // #define WT_VAR_DBNAME 0 #define WT_VAR_STABLE 1 #define WT_VAR_TABLE 2 #define WT_VAR_DNODEID 3 #define WT_VAR_USERNAME 4 #define WT_VAR_ALLTABLE 5 #define WT_VAR_FUNC 6 #define WT_VAR_KEYWORD 7 #define WT_VAR_TBACTION 8 #define WT_VAR_DBOPTION 9 #define WT_VAR_DATATYPE 10 #define WT_VAR_KEYTAGS 11 #define WT_VAR_ANYWORD 12 #define WT_VAR_CNT 13 #define WT_FROM_DB_MAX 4 // max get content from db #define WT_FROM_DB_CNT (WT_FROM_DB_MAX + 1) #define WT_TEXT 0xFF char dbName[256] = ""; // save use database name; // tire array STire* tires[WT_VAR_CNT]; pthread_mutex_t tiresMutex; //save thread handle obtain var name from db server pthread_t* threads[WT_FROM_DB_CNT]; // obtain var name with sql from server char varTypes[WT_VAR_CNT][64] = { "", "", "", "", "", "", "", "", "", "", "", "", "" }; char varSqls[WT_FROM_DB_CNT][64] = { "show databases;", "show stables;", "show tables;", "show dnodes;", "show users;" }; // var words current cursor, if user press any one key except tab, cursorVar can be reset to -1 int cursorVar = -1; bool varMode = false; // enter var names list mode TAOS* varCon = NULL; Command* varCmd = NULL; SMatch* lastMatch = NULL; // save last match result int cntDel = 0; // delete byte count after next press tab // show auto tab introduction void printfIntroduction() { printf(" ********************* How to Use TAB in TAOS Shell ******************************\n"); printf(" * Taos shell supports pressing TAB key to complete word. You can try it. *\n"); printf(" * Press TAB key anywhere, You'll get surprise. *\n"); printf(" * KEYBOARD SHORTCUT: *\n"); printf(" * [ TAB ] ...... Complete the word or show help if no input *\n"); printf(" * [ Ctrl + A ] ...... move cursor to [A]head of line *\n"); printf(" * [ Ctrl + E ] ...... move cursor to [E]nd of line *\n"); printf(" * [ Ctrl + W ] ...... move cursor to line of middle *\n"); printf(" * [ Ctrl + L ] ...... clean screen *\n"); printf(" * [ Ctrl + K ] ...... clean after cursor *\n"); printf(" * [ Ctrl + U ] ...... clean before cursor *\n"); printf(" * *\n"); printf(" **********************************************************************************\n\n"); } void showHelp() { printf("\nThe following are supported commands for Taos shell:"); printf("\n\ ----- A ----- \n\ alter database \n\ alter dnode balance \n\ alter dnode resetlog;\n\ alter dnode DebugFlag 143;\n\ alter dnode monitor 1;\n\ alter table ADD COLUMN ; \n\ alter table DROP COLUMN ; \n\ alter table MODIFY COLUMN ;\n\ alter local resetlog; \n\ alter local DebugFlag 143; \n\ alter topic \n\ alter user pass ;\n\ alter user tags ;\n\ alter user privilege read ;\n\ alter user privilege write ;\n\ ----- C ----- \n\ create table using tags ...\n\ create database ;\n\ create table as ...\n\ create dnode \n\ create topic \n\ create function \n\ create user pass ;\n\ create user pass tags ;\n\ compact vnode in (vgid,vgid,vgid);\n\ ----- D ----- \n\ describe ;\n\ delete from where ... \n\ drop database ;\n\ drop table ;\n\ drop dnode ;\n\ drop function ;\n\ drop topic ;\n\ drop user ;\n\ ----- K ----- \n\ kill connection ; \n\ kill query ; \n\ kill stream ; \n\ ----- S ----- \n\ select * from where ... \n\ select _block_dist() from ;\n\ select client_version();\n\ select current_user();\n\ select database;\n\ select server_version();\n\ set max_binary_display_width ; \n\ show create database ;\n\ show create stable ;\n\ show create table ;\n\ show connections;\n\ show databases;\n\ show dnodes;\n\ show functions;\n\ show modules;\n\ show mnodes;\n\ show queries;\n\ show stables;\n\ show stables like ''; note: regular expression only support '_' and '%%' match.\n\ show streams;\n\ show scores;\n\ show tables;\n\ show tables like ''; \n\ show users;\n\ show variables;\n\ show vgroups;\n\ ----- I ----- \n\ insert into values(...) ;\n\ ----- U ----- \n\ use ;"); printf("\n\n"); //define in getDuration() function printf("\ Timestamp expression Format:\n\ b - nanosecond \n\ u - microsecond \n\ a - millisecond \n\ s - second \n\ m - minute \n\ h - hour \n\ d - day \n\ w - week \n\ now - current time \n\ Example : \n\ select * from t1 where ts > now - 2w + 3d and ts <= now - 1w -2h ;\n"); printf("\n"); } // // ------------------- parse words -------------------------- // #define SHELL_COMMAND_COUNT() (sizeof(shellCommands) / sizeof(SWords)) // get at SWord * atWord(SWords * command, int32_t index) { SWord * word = command->head; for (int32_t i = 0; i < index; i++) { if (word == NULL) return NULL; word = word->next; } return word; } #define MATCH_WORD(x) atWord(x, x->matchIndex) int wordType(const char* p, int32_t len) { for (int i = 0; i < WT_VAR_CNT; i++) { if (strncmp(p, varTypes[i], len) == 0) return i; } return WT_TEXT; } // add word SWord * addWord(const char* p, int32_t len, bool pattern) { SWord* word = (SWord *) malloc(sizeof(SWord)); memset(word, 0, sizeof(SWord)); word->word = (char* )p; word->len = len; // check format if (pattern) { word->type = wordType(p, len); } else { word->type = WT_TEXT; } return word; } // parse one command void parseCommand(SWords * command, bool pattern) { char * p = command->source; int32_t start = 0; int32_t size = command->source_len > 0 ? command->source_len : strlen(p); bool lastBlank = false; for (int i = 0; i <= size; i++) { if (p[i] == ' ' || i == size) { // check continue blank like ' ' if (p[i] == ' ') { if (lastBlank) { start ++; continue; } if (i == 0) { // first blank lastBlank = true; start ++; continue; } lastBlank = true; } // found split or string end , append word if (command->head == NULL) { command->head = addWord(p + start, i - start, pattern); command->count = 1; } else { SWord * word = command->head; while (word->next) { word = word->next; } word->next = addWord(p + start, i - start, pattern); command->count ++; } start = i + 1; } else { lastBlank = false; } } } // free Command void freeCommand(SWords * command) { SWord * word = command->head; if (word == NULL) { return ; } // loop while (word->next) { SWord * tmp = word; word = word->next; // if malloc need free if(tmp->free && tmp->word) free(tmp->word); free(tmp); } // if malloc need free if(word->free && word->word) free(word->word); free(word); } void GenerateVarType(int type, char** p, int count) { STire* tire = createTire(TIRE_LIST); for (int i = 0; i < count; i++) { insertWord(tire, p[i]); } pthread_mutex_lock(&tiresMutex); tires[type] = tire; pthread_mutex_unlock(&tiresMutex); } // // -------------------- shell auto ---------------- // // init shell auto funciton , shell start call once bool shellAutoInit() { // command int32_t count = SHELL_COMMAND_COUNT(); for (int32_t i = 0; i < count; i ++) { parseCommand(shellCommands + i, true); } // tires memset(tires, 0, sizeof(STire*) * WT_VAR_CNT); pthread_mutex_init(&tiresMutex, NULL); // threads memset(threads, 0, sizeof(pthread_t*) * WT_FROM_DB_CNT); // generate varType GenerateVarType(WT_VAR_FUNC, functions, sizeof(functions) /sizeof(char *)); GenerateVarType(WT_VAR_KEYWORD, keywords, sizeof(keywords) /sizeof(char *)); GenerateVarType(WT_VAR_DBOPTION, db_options, sizeof(db_options) /sizeof(char *)); GenerateVarType(WT_VAR_TBACTION, tb_actions, sizeof(tb_actions) /sizeof(char *)); GenerateVarType(WT_VAR_DATATYPE, data_types, sizeof(data_types) /sizeof(char *)); GenerateVarType(WT_VAR_KEYTAGS, key_tags, sizeof(key_tags) /sizeof(char *)); printfIntroduction(); return true; } // exit shell auto funciton, shell exit call once void shellAutoExit() { // free command int32_t count = SHELL_COMMAND_COUNT(); for (int32_t i = 0; i < count; i ++) { freeCommand(shellCommands + i); } // free tires pthread_mutex_lock(&tiresMutex); for (int32_t i = 0; i < WT_VAR_CNT; i++) { if (tires[i]) { freeTire(tires[i]); tires[i] = NULL; } } pthread_mutex_unlock(&tiresMutex); // destory pthread_mutex_destroy(&tiresMutex); // free threads for (int32_t i = 0; i < WT_VAR_CNT; i++) { if (threads[i]) { taosDestroyThread(threads[i]); threads[i] = NULL; } } // free lastMatch if (lastMatch) { freeMatch(lastMatch); lastMatch = NULL; } } // // ------------------- auto ptr for tires -------------------------- // bool setNewAuotPtr(int type, STire* pNew) { if (pNew == NULL) return false; pthread_mutex_lock(&tiresMutex); STire* pOld = tires[type]; if (pOld != NULL) { // previous have value, release self ref count if (--pOld->ref == 0) { freeTire(pOld); } } // set new tires[type] = pNew; tires[type]->ref = 1; pthread_mutex_unlock(&tiresMutex); return true; } // get ptr STire* getAutoPtr(int type) { if (tires[type] == NULL) { return NULL; } pthread_mutex_lock(&tiresMutex); tires[type]->ref++; pthread_mutex_unlock(&tiresMutex); return tires[type]; } // put back tire to tires[type], if tire not equal tires[type].p, need free tire void putBackAutoPtr(int type, STire* tire) { if (tire == NULL) { return ; } pthread_mutex_lock(&tiresMutex); if (tires[type] != tire) { //update by out, can't put back , so free if (--tire->ref == 1) { // support multi thread getAuotPtr freeTire(tire); } } else { tires[type]->ref--; assert(tires[type]->ref > 0); } pthread_mutex_unlock(&tiresMutex); return ; } // // ------------------- var Word -------------------------- // #define MAX_CACHED_CNT 100000 // max cached rows 10w // write sql result to var name, return write rows cnt int writeVarNames(int type, TAOS_RES* tres) { // fetch row TAOS_ROW row = taos_fetch_row(tres); if (row == NULL) { return 0; } TAOS_FIELD *fields = taos_fetch_fields(tres); // create new tires char tireType = type == WT_VAR_TABLE ? TIRE_TREE : TIRE_LIST; STire* tire = createTire(tireType); // enum rows char name[1024]; int numOfRows = 0; do { int32_t* lengths = taos_fetch_lengths(tres); int32_t bytes = lengths[0]; if(fields[0].type == TSDB_DATA_TYPE_SMALLINT) { sprintf(name,"%d", *(int16_t*)row[0]); } else { memcpy(name, row[0], bytes); } name[bytes] = 0; //set string end // insert to tire insertWord(tire, name); if (++numOfRows > MAX_CACHED_CNT ) { break; } row = taos_fetch_row(tres); } while (row != NULL); // replace old tire setNewAuotPtr(type, tire); return numOfRows; } bool firstMatchCommand(TAOS * con, Command * cmd); // // thread obtain var thread from db server // void* varObtainThread(void* param) { int type = *(int* )param; free(param); if (varCon == NULL || type > WT_FROM_DB_MAX) { return NULL; } TAOS_RES* pSql = taos_query_h(varCon, varSqls[type], NULL); if (taos_errno(pSql)) { taos_free_result(pSql); return NULL; } // write var names from pSql int cnt = writeVarNames(type, pSql); // free sql taos_free_result(pSql); // check need call auto tab if (cnt > 0 && waitAutoFill) { // press tab key by program firstMatchCommand(varCon, varCmd); } return NULL; } // only match next one word from all match words, return valuue must free by caller char* matchNextPrefix(STire* tire, char* pre) { SMatch* match = NULL; // re-use last result if (lastMatch) { if (strcmp(pre, lastMatch->pre) == 0) { // same pre match = lastMatch; } } if (match == NULL) { // not same with last result if (pre[0] == 0) { // EMPTY PRE match = enumAll(tire); } else { // NOT EMPTY match = matchPrefix(tire, pre, NULL); } // save to lastMatch if (match) { if (lastMatch) freeMatch(lastMatch); lastMatch = match; } } // check valid if (match == NULL || match->head == NULL) { // no one matched return false; } if (cursorVar == -1) { // first cursorVar = 0; return strdup(match->head->word); } // according to cursorVar , calculate next one int i = 0; SMatchNode* item = match->head; while (item) { if (i == cursorVar + 1) { // found next position ok if (item->next == NULL) { // match last item, reset cursorVar to head cursorVar = -1; } else { cursorVar = i; } return strdup(item->word); } // check end item if (item->next == NULL) { // if cursorVar > var list count, return last and reset cursorVar cursorVar = -1; return strdup(item->word); } // move next item = item->next; i++; } return NULL; } // search pre word from tire tree, return value must free by caller char* tireSearchWord(int type, char* pre) { if (type == WT_TEXT) { return NULL; } if(type > WT_FROM_DB_MAX) { // NOT FROM DB , tires[type] alwary not null STire* tire = tires[type]; if (tire == NULL) return NULL; return matchNextPrefix(tire, pre); } // TYPE CONTEXT GET FROM DB pthread_mutex_lock(&tiresMutex); // check need obtain from server if (tires[type] == NULL) { waitAutoFill = true; // need async obtain var names from db sever if (threads[type] != NULL) { if (taosThreadRunning(threads[type])) { // thread running , need not obtain again, return pthread_mutex_unlock(&tiresMutex); return NULL; } // destroy previous thread handle for new create thread handle taosDestroyThread(threads[type]); threads[type] = NULL; } // create new void * param = malloc(sizeof(int)); *((int* )param) = type; threads[type] = taosCreateThread(varObtainThread, param); pthread_mutex_unlock(&tiresMutex); return NULL; } pthread_mutex_unlock(&tiresMutex); // can obtain var names from local STire* tire = getAutoPtr(type); if (tire == NULL) { return NULL; } char* str = matchNextPrefix(tire, pre); // used finish, put back pointer to autoptr array putBackAutoPtr(type, tire); return str; } // match var word, word1 is pattern , word2 is input from shell bool matchVarWord(SWord* word1, SWord* word2) { // search input word from tire tree char pre[512]; memcpy(pre, word2->word, word2->len); pre[word2->len] = 0; char* str = NULL; if (word1->type == WT_VAR_ALLTABLE) { // ALL_TABLE str = tireSearchWord(WT_VAR_STABLE, pre); if (str == NULL) { str = tireSearchWord(WT_VAR_TABLE, pre); if(str == NULL) return false; } } else { // OTHER str = tireSearchWord(word1->type, pre); if (str == NULL) { // not found or word1->type variable list not obtain from server, return not match return false; } } // free previous malloc if(word1->free && word1->word) { free(word1->word); } // save word1->word = str; word1->len = strlen(str); word1->free = true; // need free return true; } // // ------------------- match words -------------------------- // // compare command cmd1 come from shellCommands , cmd2 come from user input int32_t compareCommand(SWords * cmd1, SWords * cmd2) { SWord * word1 = cmd1->head; SWord * word2 = cmd2->head; if (word1 == NULL || word2 == NULL) { return -1; } for (int32_t i = 0; i < cmd1->count; i++) { if (word1->type == WT_TEXT) { // WT_TEXT match if (word1->len == word2->len) { if (strncasecmp(word1->word, word2->word, word1->len) != 0) return -1; } else if (word1->len < word2->len) { return -1; } else { // word1->len > word2->len if (strncasecmp(word1->word, word2->word, word2->len) == 0) { cmd1->matchIndex = i; cmd1->matchLen = word2->len; return i; } else { return -1; } } } else { // WT_VAR auto match any one word if (word2->next == NULL) { // input words last one if (matchVarWord(word1, word2)) { cmd1->matchIndex = i; cmd1->matchLen = word2->len; varMode = true; return i; } return -1; } } // move next word1 = word1->next; word2 = word2->next; if (word1 == NULL || word2 == NULL) { return -1; } } return -1; } // match command SWords * matchCommand(SWords * input, bool continueSearch) { int32_t count = SHELL_COMMAND_COUNT(); for (int32_t i = 0; i < count; i ++) { SWords * shellCommand = shellCommands + i; if (continueSearch && lastMatchIndex != -1 && i <= lastMatchIndex) { // new match must greate than lastMatchIndex if (varMode && i == lastMatchIndex) { // do nothing, var match on lastMatchIndex } else { continue; } } // command is large if (input->count > shellCommand->count ) { continue; } // compare int32_t index = compareCommand(shellCommand, input); if (index != -1) { if (firstMatchIndex == -1) firstMatchIndex = i; curMatchIndex = i; return &shellCommands[i]; } } // not match return NULL; } // // ------------------- print screen -------------------------- // // delete char count void deleteCount(Command * cmd, int count) { int size = 0; int width = 0; clearScreen(cmd->endOffset + prompt_size, cmd->screenOffset + prompt_size); // loop delete while (--count >= 0 && cmd->cursorOffset > 0) { getPrevCharSize(cmd->command, cmd->cursorOffset, &size, &width); memmove(cmd->command + cmd->cursorOffset - size, cmd->command + cmd->cursorOffset, cmd->commandSize - cmd->cursorOffset); cmd->commandSize -= size; cmd->cursorOffset -= size; cmd->screenOffset -= width; cmd->endOffset -= width; } } // show screen void printScreen(TAOS * con, Command * cmd, SWords * match) { // modify Command if (firstMatchIndex == -1 || curMatchIndex == -1) { // no match return ; } // first tab press const char * str = NULL; int strLen = 0; if (firstMatchIndex == curMatchIndex && lastWordBytes == -1) { // first press tab SWord * word = MATCH_WORD(match); str = word->word + match->matchLen; strLen = word->len - match->matchLen; lastMatchIndex = firstMatchIndex; lastWordBytes = word->len; } else { if (lastWordBytes == -1) return ; deleteCount(cmd, lastWordBytes); SWord * word = MATCH_WORD(match); str = word->word; strLen = word->len; // set current to last lastMatchIndex = curMatchIndex; lastWordBytes = word->len; } // insert new insertChar(cmd, (char *)str, strLen); } // main key press tab , matched return true else false bool firstMatchCommand(TAOS * con, Command * cmd) { // parse command SWords* input = (SWords *)malloc(sizeof(SWords)); memset(input, 0, sizeof(SWords)); input->source = cmd->command; input->source_len = cmd->commandSize; parseCommand(input, false); // if have many , default match first, if press tab again , switch to next curMatchIndex = -1; lastMatchIndex = -1; SWords * match = matchCommand(input, true); if (match == NULL) { // not match , nothing to do freeCommand(input); free(input); return false; } // print to screen printScreen(con, cmd, match); freeCommand(input); free(input); return true; } // create input source void createInputFromFirst(SWords* input, SWords * firstMatch) { // // if next pressTabKey , input context come from firstMatch, set matched length with source_len // input->source = (char*)malloc(1024); memset((void* )input->source, 0, 1024); SWord * word = firstMatch->head; // source_len = full match word->len + half match with firstMatch->matchLen for (int i = 0; i < firstMatch->matchIndex && word; i++) { // combine source from each word strncpy(input->source + input->source_len, word->word, word->len); strcat(input->source, " "); // append blank splite input->source_len += word->len + 1; // 1 is blank length // move next word = word->next; } // appand half matched word for last if (word) { strncpy(input->source + input->source_len, word->word, firstMatch->matchLen); input->source_len += firstMatch->matchLen; } } // user press Tabkey again is named next , matched return true else false bool nextMatchCommand(TAOS * con, Command * cmd, SWords * firstMatch) { if (firstMatch == NULL || firstMatch->head == NULL) { return false; } SWords* input = (SWords *)malloc(sizeof(SWords)); memset(input, 0, sizeof(SWords)); // create input from firstMatch createInputFromFirst(input, firstMatch); // parse input parseCommand(input, false); // if have many , default match first, if press tab again , switch to next SWords * match = matchCommand(input, true); if (match == NULL) { // if not match , reset all index firstMatchIndex = -1; curMatchIndex = -1; match = matchCommand(input, false); if (match == NULL) { freeCommand(input); if (input->source) free(input->source); free(input); return false; } } // print to screen printScreen(con, cmd, match); // free if (input->source) { free(input->source); input->source = NULL; } freeCommand(input); free(input); return true; } // fill with type bool fillWithType(TAOS * con, Command * cmd, char* pre, int type) { // get type STire* tire = tires[type]; char* str = matchNextPrefix(tire, pre); if (str == NULL) { return false; } // need insert part string char * part = str + strlen(pre); // show int count = strlen(part); insertChar(cmd, part, count); cntDel = count; // next press tab delete current append count free(str); return true; } // fill with type bool fillTableName(TAOS * con, Command * cmd, char* pre) { // search stable and table char * str = tireSearchWord(WT_VAR_STABLE, pre); if (str == NULL) { str = tireSearchWord(WT_VAR_TABLE, pre); if(str == NULL) return false; } // need insert part string char * part = str + strlen(pre); // delete autofill count last append if(cntDel > 0) { deleteCount(cmd, cntDel); cntDel = 0; } // show int count = strlen(part); insertChar(cmd, part, count); cntDel = count; // next press tab delete current append count free(str); return true; } // // find last word from sql select clause // example : // 1 select cou -> press tab select count( // 2 select count(*),su -> select count(*), sum( // 3 select count(*), su -> select count(*), sum( // char * lastWord(char * p) { // get near from end revert find ' ' and ',' char * p1 = strrchr(p, ' '); char * p2 = strrchr(p, ','); if (p1 && p2) { return MAX(p1, p2) + 1; } else if (p1) { return p1 + 1; } else if(p2) { return p2 + 1; } else { return p; } } bool fieldsInputEnd(char* sql) { // not in '()' char* p1 = strrchr(sql, '('); char* p2 = strrchr(sql, ')'); if (p1 && p2 == NULL) { // like select count( ' ' return false; } else if (p1 && p2 && p1 > p2) { // like select sum(age), count( ' ' return false; } // not in ',' char * p3 = strrchr(sql, ','); char * p = p3; // like select ts, age,' ' if (p) { ++p; bool allBlank = true; // after last ',' all char is blank int cnt = 0; // blank count , like ' ' as one blank char * plast = NULL; // last blank position while(*p) { if (*p == ' ') { plast = p; cnt ++; } else { allBlank = false; } ++p; } // any one word is not blank if(allBlank) { return false; } // like 'select count(*),sum(age) fr' need return true if (plast && plast > p3 && p2 > p1 && plast > p2 && p1 > p3) { return true; } // if last char not ' ', then not end field, like 'select count(*), su' can fill sum( if(sql[strlen(sql)-1] != ' ' && cnt <= 1) { return false; } } char * p4 = strrchr(sql, ' '); if(p4 == NULL) { // only one word return false; } return true; } // need insert from bool needInsertFrom(char * sql, int len) { // last is blank if(sql[len-1] != ' ') { // insert from keyword return false; } // select fields input is end if (!fieldsInputEnd(sql)) { return false; } // can insert from keyword return true; } bool matchSelectQuery(TAOS * con, Command * cmd) { // if continue press Tab , delete bytes by previous autofill if (cntDel > 0) { deleteCount(cmd, cntDel); cntDel = 0; } // match select ... int len = cmd->commandSize; char * p = cmd->command; // remove prefix blank while (p[0] == ' ' && len > 0) { p++; len--; } // special range if(len < 7 || len > 512) { return false; } // select and from if(strncasecmp(p, "select ", 7) != 0) { // not select query clause return false; } p += 7; len -= 7; char* ps = p = strndup(p, len); // union all char * p1; do { p1 = strstr(p, UNION_ALL); if(p1) { p = p1 + strlen(UNION_ALL); } } while (p1); char * from = strstr(p, " from "); //last word , maybe empty string or some letters of a string char * last = lastWord(p); bool ret = false; if (from == NULL) { bool fieldEnd = fieldsInputEnd(p); // cheeck fields input end then insert from keyword if (fieldEnd && p[len-1] == ' ') { insertChar(cmd, "from", 4); free(ps); return true; } // fill funciton if(fieldEnd) { // fields is end , need match keyword ret = fillWithType(con, cmd, last, WT_VAR_KEYWORD); } else { ret = fillWithType(con, cmd, last, WT_VAR_FUNC); } free(ps); return ret; } // have from char * blank = strstr(from + 6, " "); if (blank == NULL) { // no table name, need fill ret = fillTableName(con, cmd, last); } else { ret = fillWithType(con, cmd, last, WT_VAR_KEYWORD); } free(ps); return ret; } // if is input create fields or tags area, return true bool isCreateFieldsArea(char * p) { char * left = strrchr(p, '('); if (left == NULL) { // like 'create table st' return false; } char * right = strrchr(p, ')'); if(right == NULL) { // like 'create table st( ' return true; } if (left > right) { // like 'create table st( ts timestamp, age int) tags(area ' return true; } return false; } bool matchCreateTable(TAOS * con, Command * cmd) { // if continue press Tab , delete bytes by previous autofill if (cntDel > 0) { deleteCount(cmd, cntDel); cntDel = 0; } // match select ... int len = cmd->commandSize; char * p = cmd->command; // remove prefix blank while (p[0] == ' ' && len > 0) { p++; len--; } // special range if(len < 7 || len > 1024) { return false; } // select and from if(strncasecmp(p, "create table ", 13) != 0) { // not select query clause return false; } p += 13; len -= 13; char* ps = strndup(p, len); bool ret = false; char * last = lastWord(ps); // check in create fields or tags input area if (isCreateFieldsArea(ps)) { ret = fillWithType(con, cmd, last, WT_VAR_DATATYPE); } // tags if (!ret) { // find only one ')' , can insert tags char * p1 = strchr(ps, ')'); if (p1) { if(strchr(p1 + 1, ')') == NULL && strstr(p1 + 1, "tags") == NULL) { // can insert tags keyword ret = fillWithType(con, cmd, last, WT_VAR_KEYTAGS); } } } free(ps); return ret; } bool matchOther(TAOS * con, Command * cmd) { int len = cmd->commandSize; char* p = cmd->command; if (p[len - 1] == '\\') { // append '\G' char a[] = "G;"; insertChar(cmd, a, 2); return true; } return false; } // main key press tab void pressTabKey(TAOS * con, Command * cmd) { // check if (cmd->commandSize == 0) { // empty showHelp(); showOnScreen(cmd); return ; } // save connection to global varCon = con; varCmd = cmd; bool matched = false; // manual match like create table st( ... matched = matchCreateTable(con, cmd); if (matched) return ; // shellCommands match if (firstMatchIndex == -1) { matched = firstMatchCommand(con, cmd); } else { matched = nextMatchCommand(con, cmd, &shellCommands[firstMatchIndex]); } if (matched) return ; // NOT MATCHED ANYONE // match other like '\G' ... matched = matchOther(con, cmd); if (matched) return ; // manual match like select * from ... matched = matchSelectQuery(con, cmd); if (matched) return ; return ; } // press othr key void pressOtherKey(char c) { // reset global variant firstMatchIndex = -1; lastMatchIndex = -1; curMatchIndex = -1; lastWordBytes = -1; // var names cursorVar = -1; varMode = false; waitAutoFill = false; cntDel = 0; if (lastMatch) { freeMatch(lastMatch); lastMatch = NULL; } } // put name into name, return name length int getWordName(char* p, char * name, int nameLen) { //remove prefix blank while (*p == ' ') { p++; } // get databases name; int i = 0; while(p[i] != 0 && i < nameLen - 1) { name[i] = p[i]; i++; if(p[i] == ' ' || p[i] == ';'|| p[i] == '(') { // name end break; } } name[i] = 0; return i; } // deal use db, if have 'use' return true bool dealUseDB(char * sql) { // check use keyword if(strncasecmp(sql, "use ", 4) != 0) { return false; } char db[256]; char *p = sql + 4; if (getWordName(p, db, sizeof(db)) == 0) { // no name , return return true; } // dbName is previous use open db name if (strcasecmp(db, dbName) == 0) { // same , no need switch return true; } // switch new db pthread_mutex_lock(&tiresMutex); // STABLE set null STire* tire = tires[WT_VAR_STABLE]; tires[WT_VAR_STABLE] = NULL; if(tire) { freeTire(tire); } // TABLE set null tire = tires[WT_VAR_TABLE]; tires[WT_VAR_TABLE] = NULL; if(tire) { freeTire(tire); } // save strcpy(dbName, db); pthread_mutex_unlock(&tiresMutex); return true; } // deal create, if have 'create' return true bool dealCreateCommand(char * sql) { // check keyword if(strncasecmp(sql, "create ", 7) != 0) { return false; } char name[1024]; char *p = sql + 7; if (getWordName(p, name, sizeof(name)) == 0) { // no name , return return true; } int type = -1; // dbName is previous use open db name if (strcasecmp(name, "database") == 0) { type = WT_VAR_DBNAME; } else if (strcasecmp(name, "table") == 0) { if(strstr(sql, " tags") != NULL && strstr(sql, " using ") == NULL) type = WT_VAR_STABLE; else type = WT_VAR_TABLE; } else if (strcasecmp(name, "user") == 0) { type = WT_VAR_USERNAME; } else { // no match , return return true; } // move next p += strlen(name); // get next word , that is table name if (getWordName(p, name, sizeof(name)) == 0) { // no name , return return true; } // switch new db pthread_mutex_lock(&tiresMutex); // STABLE set null STire* tire = tires[type]; if(tire) { insertWord(tire, name); } pthread_mutex_unlock(&tiresMutex); return true; } // deal create, if have 'drop' return true bool dealDropCommand(char * sql) { // check keyword if(strncasecmp(sql, "drop ", 5) != 0) { return false; } char name[1024]; char *p = sql + 5; if (getWordName(p, name, sizeof(name)) == 0) { // no name , return return true; } int type = -1; // dbName is previous use open db name if (strcasecmp(name, "database") == 0) { type = WT_VAR_DBNAME; } else if (strcasecmp(name, "table") == 0) { type = WT_VAR_ALLTABLE; } else if (strcasecmp(name, "dnode") == 0) { type = WT_VAR_DNODEID; } else if (strcasecmp(name, "user") == 0) { type = WT_VAR_USERNAME; } else { // no match , return return true; } // move next p += strlen(name); // get next word , that is table name if (getWordName(p, name, sizeof(name)) == 0) { // no name , return return true; } // switch new db pthread_mutex_lock(&tiresMutex); // STABLE set null if(type == WT_VAR_ALLTABLE) { bool del = false; // del in stable STire* tire = tires[WT_VAR_STABLE]; if(tire) del = deleteWord(tire, name); // del in table if(!del) { tire = tires[WT_VAR_TABLE]; if(tire) del = deleteWord(tire, name); } } else { // OTHER TYPE STire* tire = tires[type]; if(tire) deleteWord(tire, name); } pthread_mutex_unlock(&tiresMutex); return true; } // callback autotab module after shell sql execute void callbackAutoTab(char* sqlstr, TAOS* pSql, bool usedb) { char * sql = sqlstr; // remove prefix blank while (*sql == ' ') { sql++; } if(dealUseDB(sql)) { // change to new db return ; } // create command add name to autotab if(dealCreateCommand(sql)) { return ; } // drop command remove name from autotab if(dealDropCommand(sql)) { return ; } return ; }