/* * 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 "shellInt.h" #include "shellAuto.h" #include "shellTire.h" #include "tthread.h" // // ------------- define area --------------- // #define UNION_ALL " union all " // extern function void shellClearScreen(int32_t ecmd_pos, int32_t cursor_pos); void shellGetPrevCharSize(const char* str, int32_t pos, int32_t* size, int32_t* width); void shellShowOnScreen(SShellCmd* cmd); void shellInsertChar(SShellCmd* cmd, char* c, int size); void shellInsertStr(SShellCmd* cmd, char* str, int size); bool appendAfterSelect(TAOS* con, SShellCmd* cmd, char* p, int32_t len); 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 \"resetlog\";", 0, 0, NULL}, {"alter dnode \"debugFlag\" \"141\";", 0, 0, NULL}, {"alter dnode \"monitor\" \"0\";", 0, 0, NULL}, {"alter dnode \"monitor\" \"1\";", 0, 0, NULL}, {"alter all dnodes \"resetlog\";", 0, 0, NULL}, {"alter all dnodes \"debugFlag\" \"141\";", 0, 0, NULL}, {"alter all dnodes \"monitor\" \"0\";", 0, 0, NULL}, {"alter all dnodes \"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 ;", 0, 0, NULL}, // 20 {"create table using tags(", 0, 0, NULL}, {"create database " " " " ;", 0, 0, NULL}, {"create dnode ", 0, 0, NULL}, {"create index ", 0, 0, NULL}, {"create mnode on dnode ;", 0, 0, NULL}, {"create qnode on dnode ;", 0, 0, NULL}, {"create stream into as select", 0, 0, NULL}, // 26 append sub sql {"create topic as select", 0, 0, NULL}, // 27 append sub sql {"create function ", 0, 0, NULL}, {"create user pass sysinfo 0;", 0, 0, NULL}, {"create user pass sysinfo 1;", 0, 0, NULL}, {"describe ", 0, 0, NULL}, {"delete from where ", 0, 0, NULL}, {"drop database ", 0, 0, NULL}, {"drop table ", 0, 0, NULL}, {"drop dnode ", 0, 0, NULL}, {"drop mnode on dnode ;", 0, 0, NULL}, {"drop qnode on dnode ;", 0, 0, NULL}, {"drop user ;", 0, 0, NULL}, // 40 {"drop function", 0, 0, NULL}, {"drop consumer group on ", 0, 0, NULL}, {"drop topic ;", 0, 0, NULL}, {"drop stream ;", 0, 0, NULL}, {"explain select", 0, 0, NULL}, // 44 append sub sql {"flush database ;", 0, 0, NULL}, {"help;", 0, 0, NULL}, {"grant all on to ;", 0, 0, NULL}, {"grant read on to ;", 0, 0, NULL}, {"grant write on to ;", 0, 0, NULL}, {"kill connection ;", 0, 0, NULL}, {"kill query ", 0, 0, NULL}, {"kill transaction ", 0, 0, NULL}, {"merge vgroup ", 0, 0, NULL}, {"reset query cache;", 0, 0, NULL}, {"revoke all on from ;", 0, 0, NULL}, {"revoke read on from ;", 0, 0, NULL}, {"revoke write on from ;", 0, 0, NULL}, {"select * from ", 0, 0, NULL}, {"select client_version();", 0, 0, NULL}, // 60 {"select current_user();", 0, 0, NULL}, {"select database();", 0, 0, NULL}, {"select server_version();", 0, 0, NULL}, {"select server_status();", 0, 0, NULL}, {"select now();", 0, 0, NULL}, {"select today();", 0, 0, NULL}, {"select timezone();", 0, 0, NULL}, {"set max_binary_display_width ", 0, 0, NULL}, {"show apps;", 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 cluster;", 0, 0, NULL}, {"show databases;", 0, 0, NULL}, {"show dnodes;", 0, 0, NULL}, {"show dnode variables;", 0, 0, NULL}, {"show functions;", 0, 0, NULL}, {"show mnodes;", 0, 0, NULL}, {"show queries;", 0, 0, NULL}, // 80 {"show query ;", 0, 0, NULL}, {"show qnodes;", 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 snodes;", 0, 0, NULL}, {"show subscriptions;", 0, 0, NULL}, {"show tables;", 0, 0, NULL}, {"show tables like", 0, 0, NULL}, {"show table distributed ", 0, 0, NULL}, {"show tags from ", 0, 0, NULL}, {"show tags from ", 0, 0, NULL}, {"show topics;", 0, 0, NULL}, {"show transactions;", 0, 0, NULL}, {"show users;", 0, 0, NULL}, {"show variables;", 0, 0, NULL}, {"show local variables;", 0, 0, NULL}, {"show vnodes ", 0, 0, NULL}, {"show vgroups;", 0, 0, NULL}, {"show consumers;", 0, 0, NULL}, {"show grants;", 0, 0, NULL}, {"split vgroup ", 0, 0, NULL}, {"insert into values(", 0, 0, NULL}, {"insert into using tags(", 0, 0, NULL}, {"insert into using values(", 0, 0, NULL}, {"insert into file ", 0, 0, NULL}, {"trim database ", 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 ", "partition by "}; 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 ", "rename column ", "add tag ", "modify tag ", "drop tag ", "rename tag ", "set tag ", }; char* user_actions[] = {"pass ", "enable ", "sysinfo "}; char* tb_options[] = {"comment ", "watermark ", "max_delay ", "ttl ", "rollup(", "sma("}; char* db_options[] = {"keep ", "replica ", "precision ", "strict ", "buffer ", "cachemodel ", "cachesize ", "comp ", "duration ", "wal_fsync_period", "maxrows ", "minrows ", "pages ", "pagesize ", "retentions ", "wal_level ", "vgroups ", "single_stable ", "wal_retention_period ", "wal_roll_period ", "wal_retention_size ", "wal_segment_size "}; char* alter_db_options[] = {"cachemodel ", "replica ", "keep ", "cachesize ", "wal_fsync_period ", "wal_level "}; char* data_types[] = {"timestamp", "int", "int unsigned", "varchar(16)", "float", "double", "binary(16)", "nchar(16)", "bigint", "bigint unsigned", "smallint", "smallint unsigned", "tinyint", "tinyint unsigned", "bool", "json"}; char* key_tags[] = {"tags("}; char* key_select[] = {"select "}; char* key_systable[] = { "ins_dnodes", "ins_mnodes", "ins_modules", "ins_qnodes", "ins_snodes", "ins_cluster", "ins_databases", "ins_functions", "ins_indexes", "ins_stables", "ins_tables", "ins_tags", "ins_users", "ins_grants", "ins_vgroups", "ins_configs", "ins_dnode_variables", "ins_topics", "ins_subscriptions", "ins_streams", "ins_stream_tasks", "ins_vnodes", "ins_user_privileges"}; // // ------- 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_TOPIC 5 #define WT_VAR_STREAM 6 #define WT_VAR_ALLTABLE 7 #define WT_VAR_FUNC 8 #define WT_VAR_KEYWORD 9 #define WT_VAR_TBACTION 10 #define WT_VAR_DBOPTION 11 #define WT_VAR_ALTER_DBOPTION 12 #define WT_VAR_DATATYPE 13 #define WT_VAR_KEYTAGS 14 #define WT_VAR_ANYWORD 15 #define WT_VAR_TBOPTION 16 #define WT_VAR_USERACTION 17 #define WT_VAR_KEYSELECT 18 #define WT_VAR_SYSTABLE 19 #define WT_VAR_CNT 20 #define WT_FROM_DB_MAX 6 // 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]; TdThreadMutex tiresMutex; // save thread handle obtain var name from db server TdThread* 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;", "show topics;", "show streams;"}; // 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; SShellCmd* 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(" ****************************** Tab Completion *************************************\n"); printf(" * The TDengine CLI supports tab completion for a variety of items, *\n"); printf(" * including database names, table names, function names and keywords. *\n"); printf(" * The full list of shortcut keys is as follows: *\n"); printf(" * [ TAB ] ...... complete the current word *\n"); printf(" * ...... if used on a blank line, display all supported commands *\n"); printf(" * [ Ctrl + A ] ...... move cursor to the st[A]rt of the line *\n"); printf(" * [ Ctrl + E ] ...... move cursor to the [E]nd of the line *\n"); printf(" * [ Ctrl + W ] ...... move cursor to the middle of the line *\n"); printf(" * [ Ctrl + L ] ...... clear the entire screen *\n"); printf(" * [ Ctrl + K ] ...... clear the screen after the cursor *\n"); printf(" * [ Ctrl + U ] ...... clear the screen before the cursor *\n"); printf(" *************************************************************************************\n\n"); } void showHelp() { printf("\nThe TDengine CLI supports the following commands:"); printf( "\n\ ----- A ----- \n\ alter database \n\ alter dnode 'resetlog';\n\ alter dnode 'monitor' '0';\n\ alter dnode 'monitor' \"1\";\n\ alter dnode \"debugflag\" \"143\";\n\ alter all dnodes \"monitor\" \"0\";\n\ alter all dnodes \"monitor\" \"1\";\n\ alter all dnodes \"resetlog\";\n\ alter all dnodes \"debugFlag\" \n\ alter table ;\n\ alter table modify column\n\ alter local \"resetlog\";\n\ alter local \"DebugFlag\" \"143\";\n\ alter topic\n\ alter user ...\n\ ----- C ----- \n\ create table using tags ...\n\ create database ...\n\ create dnode \"fqdn:port\"n\ create index ...\n\ create mnode on dnode ;\n\ create qnode on dnode ;\n\ create stream into as select ...\n\ create topic as select ...\n\ create function ...\n\ create user pass ...\n\ ----- D ----- \n\ describe \n\ delete from where ...\n\ drop database ;\n\ drop table ;\n\ drop dnode ;\n\ drop mnode on dnode ;\n\ drop qnode on dnode ;\n\ drop user ;\n\ drop function ;\n\ drop consumer group ... \n\ drop topic ;\n\ drop stream ;\n\ ----- E ----- \n\ explain select clause ...\n\ ----- F ----- \n\ flush database ;\n\ ----- H ----- \n\ help;\n\ ----- I ----- \n\ insert into values(...) ;\n\ insert into using tags(...) values(...) ;\n\ ----- G ----- \n\ grant all on to ;\n\ grant read on to ;\n\ grant write on to ;\n\ ----- K ----- \n\ kill connection ; \n\ kill query ; \n\ kill transaction ;\n\ ----- M ----- \n\ merge vgroup ...\n\ ----- R ----- \n\ reset query cache;\n\ revoke all on from ;\n\ revoke read on from ;\n\ revoke write on from ;\n\ ----- S ----- \n\ select * from where ... \n\ select client_version();\n\ select current_user();\n\ select database();\n\ select server_version();\n\ select server_status();\n\ select now();\n\ select today();\n\ select timezone();\n\ set max_binary_display_width ...\n\ show apps;\n\ show create database ;\n\ show create stable ;\n\ show create table ;\n\ show connections;\n\ show cluster;\n\ show databases;\n\ show dnodes;\n\ show dnode variables;\n\ show functions;\n\ show mnodes;\n\ show queries;\n\ show query ;\n\ show qnodes;\n\ show snodes;\n\ show stables;\n\ show stables like \n\ show streams;\n\ show scores;\n\ show subscriptions;\n\ show tables;\n\ show tables like\n\ show table distributed ;\n\ show tags from \n\ show tags from \n\ show topics;\n\ show transactions;\n\ show users;\n\ show variables;\n\ show local variables;\n\ show vnodes \n\ show vgroups;\n\ show consumers;\n\ show grants;\n\ split vgroup ...\n\ ----- T ----- \n\ trim database ;\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*)taosMemoryMalloc(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 SShellCmd void freeCommand(SWords* command) { SWord* item = command->head; command->head = NULL; // loop while (item) { SWord* tmp = item; item = item->next; // if malloc need free if (tmp->free && tmp->word) taosMemoryFree(tmp->word); taosMemoryFree(tmp); } } void GenerateVarType(int type, char** p, int count) { STire* tire = createTire(TIRE_LIST); for (int i = 0; i < count; i++) { insertWord(tire, p[i]); } taosThreadMutexLock(&tiresMutex); tires[type] = tire; taosThreadMutexUnlock(&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); taosThreadMutexInit(&tiresMutex, NULL); // threads memset(threads, 0, sizeof(TdThread*) * WT_FROM_DB_CNT); // init database and stable tireSearchWord(WT_VAR_DBNAME, ""); tireSearchWord(WT_VAR_STABLE, ""); // 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_ALTER_DBOPTION, alter_db_options, sizeof(alter_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*)); GenerateVarType(WT_VAR_TBOPTION, tb_options, sizeof(tb_options) / sizeof(char*)); GenerateVarType(WT_VAR_USERACTION, user_actions, sizeof(user_actions) / sizeof(char*)); GenerateVarType(WT_VAR_KEYSELECT, key_select, sizeof(key_select) / sizeof(char*)); GenerateVarType(WT_VAR_SYSTABLE, key_systable, sizeof(key_systable) / sizeof(char*)); return true; } // set conn void shellSetConn(TAOS* conn) { varCon = conn; } // 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 taosThreadMutexLock(&tiresMutex); for (int32_t i = 0; i < WT_VAR_CNT; i++) { if (tires[i]) { freeTire(tires[i]); tires[i] = NULL; } } taosThreadMutexUnlock(&tiresMutex); // destory taosThreadMutexDestroy(&tiresMutex); // free threads for (int32_t i = 0; i < WT_FROM_DB_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; taosThreadMutexLock(&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; taosThreadMutexUnlock(&tiresMutex); return true; } // get ptr STire* getAutoPtr(int type) { if (tires[type] == NULL) { return NULL; } taosThreadMutexLock(&tiresMutex); tires[type]->ref++; taosThreadMutexUnlock(&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; } taosThreadMutexLock(&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); } taosThreadMutexUnlock(&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_INT) { 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, SShellCmd* cmd); // // thread obtain var thread from db server // void* varObtainThread(void* param) { int type = *(int*)param; taosMemoryFree(param); if (varCon == NULL || type > WT_FROM_DB_MAX) { return NULL; } TAOS_RES* pSql = taos_query(varCon, varSqls[type]); 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 = (SMatch*)taosMemoryMalloc(sizeof(SMatch)); memset(match, 0, sizeof(SMatch)); matchPrefix(tire, pre, match); } // save to lastMatch if (match) { if (lastMatch) freeMatch(lastMatch); lastMatch = match; } } // check valid if (match == NULL || match->head == NULL) { // no one matched return NULL; } 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 taosThreadMutexLock(&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 taosThreadMutexUnlock(&tiresMutex); return NULL; } // destroy previous thread handle for new create thread handle taosDestroyThread(threads[type]); threads[type] = NULL; } // create new void* param = taosMemoryMalloc(sizeof(int)); *((int*)param) = type; threads[type] = taosCreateThread(varObtainThread, param); taosThreadMutexUnlock(&tiresMutex); return NULL; } taosThreadMutexUnlock(&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) { taosMemoryFree(word1->word); } // save word1->word = str; word1->len = strlen(str); word1->free = true; // need free return true; } // // ------------------- match words -------------------------- // // compare command cmdPattern come from shellCommands , cmdInput come from user input int32_t compareCommand(SWords* cmdPattern, SWords* cmdInput) { SWord* wordPattern = cmdPattern->head; SWord* wordInput = cmdInput->head; if (wordPattern == NULL || wordInput == NULL) { return -1; } for (int32_t i = 0; i < cmdPattern->count; i++) { if (wordPattern->type == WT_TEXT) { // WT_TEXT match if (wordPattern->len == wordInput->len) { if (strncasecmp(wordPattern->word, wordInput->word, wordPattern->len) != 0) return -1; } else if (wordPattern->len < wordInput->len) { return -1; } else { // wordPattern->len > wordInput->len if (strncasecmp(wordPattern->word, wordInput->word, wordInput->len) == 0) { if (i + 1 == cmdInput->count) { // last word return match cmdPattern->matchIndex = i; cmdPattern->matchLen = wordInput->len; return i; } else { return -1; } } else { return -1; } } } else { // WT_VAR auto match any one word if (wordInput->next == NULL) { // input words last one if (matchVarWord(wordPattern, wordInput)) { cmdPattern->matchIndex = i; cmdPattern->matchLen = wordInput->len; varMode = true; return i; } return -1; } } // move next wordPattern = wordPattern->next; wordInput = wordInput->next; if (wordPattern == NULL || wordInput == 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(SShellCmd* cmd, int count) { int size = 0; int width = 0; int prompt_size = 6; shellClearScreen(cmd->endOffset + prompt_size, cmd->screenOffset + prompt_size); // loop delete while (--count >= 0 && cmd->cursorOffset > 0) { shellGetPrevCharSize(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, SShellCmd* cmd, SWords* match) { // modify SShellCmd 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 shellInsertStr(cmd, (char*)str, strLen); } // main key press tab , matched return true else false bool firstMatchCommand(TAOS* con, SShellCmd* cmd) { // parse command SWords* input = (SWords*)taosMemoryMalloc(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); taosMemoryFree(input); return false; } // print to screen printScreen(con, cmd, match); #ifdef WINDOWS printf("\r"); shellShowOnScreen(cmd); #endif freeCommand(input); taosMemoryFree(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*)taosMemoryMalloc(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, SShellCmd* cmd, SWords* firstMatch) { if (firstMatch == NULL || firstMatch->head == NULL) { return false; } SWords* input = (SWords*)taosMemoryMalloc(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) taosMemoryFree(input->source); taosMemoryFree(input); return false; } } // print to screen printScreen(con, cmd, match); #ifdef WINDOWS printf("\r"); shellShowOnScreen(cmd); #endif // free freeCommand(input); if (input->source) { taosMemoryFree(input->source); input->source = NULL; } taosMemoryFree(input); return true; } // fill with type bool fillWithType(TAOS* con, SShellCmd* 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); shellInsertStr(cmd, part, count); cntDel = count; // next press tab delete current append count taosMemoryFree(str); return true; } // fill with type bool fillTableName(TAOS* con, SShellCmd* 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); shellInsertStr(cmd, part, count); cntDel = count; // next press tab delete current append count taosMemoryFree(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 p1 > p2 ? p1 + 1 : 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; } // p is string following select keyword bool appendAfterSelect(TAOS* con, SShellCmd* cmd, char* sql, int32_t len) { char* p = strndup(sql, 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); // check fields input end then insert from keyword if (fieldEnd && p[len - 1] == ' ') { shellInsertStr(cmd, "from", 4); taosMemoryFree(p); 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); } taosMemoryFree(p); 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); } taosMemoryFree(p); return ret; } int32_t searchAfterSelect(char* p, int32_t len) { // select * from st; if (strncasecmp(p, "select ", 7) == 0) { // check nest query char* p1 = p + 7; while (1) { char* p2 = strstr(p1, "select "); if (p2 == NULL) break; p1 = p2 + 7; } return p1 - p; } // explain as select * from st; if (strncasecmp(p, "explain select ", 15) == 0) { return 15; } char* as_pos_end = strstr(p, " as select "); if (as_pos_end == NULL) return -1; as_pos_end += 11; // create stream as select if (strncasecmp(p, "create stream ", 14) == 0) { return as_pos_end - p; ; } // create topic as select if (strncasecmp(p, "create topic ", 13) == 0) { return as_pos_end - p; } return -1; } bool matchSelectQuery(TAOS* con, SShellCmd* 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; } // search char* sql_cp = strndup(p, len); int32_t n = searchAfterSelect(sql_cp, len); taosMemoryFree(sql_cp); if (n == -1 || n > len) return false; p += n; len -= n; // append return appendAfterSelect(con, cmd, p, len); } // if is input create fields or tags area, return true bool isCreateFieldsArea(char* p) { // put to while, support like create table st(ts timestamp, bin1 binary(16), bin2 + blank + TAB char* p1 = strdup(p); bool ret = false; while (1) { char* left = strrchr(p1, '('); if (left == NULL) { // like 'create table st' ret = false; break; } char* right = strrchr(p1, ')'); if (right == NULL) { // like 'create table st( ' ret = true; break; } if (left > right) { // like 'create table st( ts timestamp, age int) tags(area ' ret = true; break; } // set string end by small for next strrchr search *left = 0; } taosMemoryFree(p1); return ret; } bool matchCreateTable(TAOS* con, SShellCmd* 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); } } } // tb options if (!ret) { // find like create talbe st (...) tags(..) char* p1 = strchr(ps, ')'); // first ')' end if (p1) { if (strchr(p1 + 1, ')')) { // second ')' end // here is tb options area, can insert option ret = fillWithType(con, cmd, last, WT_VAR_TBOPTION); } } } taosMemoryFree(ps); return ret; } bool matchOther(TAOS* con, SShellCmd* cmd) { int len = cmd->commandSize; char* p = cmd->command; // '\\' if (p[len - 1] == '\\') { // append '\G' char a[] = "G;"; shellInsertStr(cmd, a, 2); return true; } // too small if (len < 8) return false; // like 'from ( ' char* sql = strndup(p, len); char* last = lastWord(sql); if (strcmp(last, "from(") == 0) { fillWithType(con, cmd, "", WT_VAR_KEYSELECT); taosMemoryFree(sql); return true; } if (strncmp(last, "(", 1) == 0) { last += 1; } char* from = strstr(sql, " from"); // find last ' from' while (from) { char* p1 = strstr(from + 5, " from"); if (p1 == NULL) break; from = p1; } if (from) { // find next is '(' char* p2 = from + 5; bool found = false; // found 'from ... ( ...' ... is any count of blank bool found1 = false; // found '(' while (1) { if (p2 == last || *p2 == '\0') { // last word or string end if (found1) { found = true; } break; } else if (*p2 == '(') { found1 = true; } else if (*p2 == ' ') { // do nothing } else { // have any other char break; } // move next p2++; } if (found) { fillWithType(con, cmd, last, WT_VAR_KEYSELECT); taosMemoryFree(sql); return true; } } // INSERT taosMemoryFree(sql); return false; } // last match if nothing matched bool matchEnd(TAOS* con, SShellCmd* cmd) { // str dump bool ret = false; char* ps = strndup(cmd->command, cmd->commandSize); char* last = lastWord(ps); if(strlen(last) < 2 ) { goto _return; } // match database if (fillWithType(con, last, WT_VAR_DBNAME)) { ret = true; goto _return; } if (fillWithType(con, last, WT_VAR_SYSTABLE)) { ret = true; goto _return; } _return: taosMemoryFree(ps); return ret; } // main key press tab void pressTabKey(SShellCmd* cmd) { // check if (cmd->commandSize == 0) { // empty showHelp(); shellShowOnScreen(cmd); return; } // save connection to global varCmd = cmd; bool matched = false; // manual match like create table st( ... matched = matchCreateTable(varCon, cmd); if (matched) return; // shellCommands match if (firstMatchIndex == -1) { matched = firstMatchCommand(varCon, cmd); } else { matched = nextMatchCommand(varCon, cmd, &shellCommands[firstMatchIndex]); } if (matched) return; // NOT MATCHED ANYONE // match other like '\G' ... matched = matchOther(varCon, cmd); if (matched) return; // manual match like select * from ... matched = matchSelectQuery(varCon, cmd); if (matched) return; // match end matched = matchEnd(varCon, cmd); 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 taosThreadMutexLock(&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); taosThreadMutexUnlock(&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 if (strcasecmp(name, "topic") == 0) { type = WT_VAR_TOPIC; } else if (strcasecmp(name, "stream") == 0) { type = WT_VAR_STREAM; } 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 taosThreadMutexLock(&tiresMutex); // STABLE set null STire* tire = tires[type]; if (tire) { insertWord(tire, name); } taosThreadMutexUnlock(&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 if (strcasecmp(name, "topic") == 0) { type = WT_VAR_TOPIC; } else if (strcasecmp(name, "stream") == 0) { type = WT_VAR_STREAM; } 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 taosThreadMutexLock(&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); } taosThreadMutexUnlock(&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; }