shellEngine.c 26.7 KB
Newer Older
H
hzcheng 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/*
 * Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
 *
 * This program is free software: you can use, redistribute, and/or modify
 * it under the terms of the GNU Affero General Public License, version 3
 * or later ("AGPL"), as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

16 17
#define _BSD_SOURCE
#define _GNU_SOURCE
H
hzcheng 已提交
18
#define _XOPEN_SOURCE
S
slguan 已提交
19
#define _DEFAULT_SOURCE
S
Shengliang Guan 已提交
20
#include "shellInt.h"
H
hzcheng 已提交
21

22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
static bool    shellIsEmptyCommand(const char *cmd);
static int32_t shellRunSingleCommand(char *command);
static int32_t shellRunCommand(char *command);
static void    shellRunSingleCommandImp(char *command);
static char   *shellFormatTimestamp(char *buf, int64_t val, int32_t precision);
static void    shellDumpFieldToFile(TdFilePtr pFile, const char *val, TAOS_FIELD *field, int32_t length,
                                    int32_t precision);
static int32_t shellDumpResultToFile(const char *fname, TAOS_RES *tres);
static void    shellPrintNChar(const char *str, int32_t length, int32_t width);
static void    shellPrintField(const char *val, TAOS_FIELD *field, int32_t width, int32_t length, int32_t precision);
static int32_t shellVerticalPrintResult(TAOS_RES *tres);
static int32_t shellCalcColWidth(TAOS_FIELD *field, int32_t precision);
static void    shellPrintHeader(TAOS_FIELD *fields, int32_t *width, int32_t num_fields);
static int32_t shellHorizontalPrintResult(TAOS_RES *tres);
static int32_t shellDumpResult(TAOS_RES *tres, char *fname, int32_t *error_no, bool vertical);
static void    shellReadHistory();
static void    shellWriteHistory();
static void    shellPrintError(TAOS_RES *tres, int64_t st);
static bool    shellIsCommentLine(char *line);
static void    shellSourceFile(const char *file);
static void    shellGetGrantInfo();
static void    shellQueryInterruptHandler(int32_t signum, void *sigInfo, void *context);
static void    shellCleanup(void *arg);
static void   *shellCancelHandler(void *arg);
static void   *shellThreadLoop(void *arg);

bool shellIsEmptyCommand(const char *cmd) {
49 50 51
  for (char c = *cmd++; c != 0; c = *cmd++) {
    if (c != ' ' && c != '\t' && c != ';') {
      return false;
H
hzcheng 已提交
52 53
    }
  }
54
  return true;
H
hzcheng 已提交
55 56
}

57 58
int32_t shellRunSingleCommand(char *command) {
  if (shellIsEmptyCommand(command)) {
59
    return 0;
H
hzcheng 已提交
60 61
  }

62 63
  if (shellRegexMatch(command, "^[ \t]*(quit|q|exit)[ \t;]*$", REG_EXTENDED | REG_ICASE)) {
    shellWriteHistory();
64
    return -1;
65 66
  }

67
  if (shellRegexMatch(command, "^[\t ]*clear[ \t;]*$", REG_EXTENDED | REG_ICASE)) {
H
hzcheng 已提交
68
    system("clear");
69 70
    return 0;
  }
71

72
  if (shellRegexMatch(command, "^[\t ]*set[ \t]+max_binary_display_width[ \t]+(default|[1-9][0-9]*)[ \t;]*$",
73
                      REG_EXTENDED | REG_ICASE)) {
74 75
    strtok(command, " \t");
    strtok(NULL, " \t");
S
Shengliang Guan 已提交
76
    char *p = strtok(NULL, " \t");
77
    if (strncasecmp(p, "default", 7) == 0) {
78
      shell.args.displayWidth = SHELL_DEFAULT_MAX_BINARY_DISPLAY_WIDTH;
79
    } else {
80 81 82
      int32_t displayWidth = atoi(p);
      displayWidth = TRANGE(displayWidth, 1, 10 * 1024);
      shell.args.displayWidth = displayWidth;
83
    }
84 85
    return 0;
  }
86

87
  if (shellRegexMatch(command, "^[ \t]*source[\t ]+[^ ]+[ \t;]*$", REG_EXTENDED | REG_ICASE)) {
H
hzcheng 已提交
88 89 90 91 92
    /* If source file. */
    char *c_ptr = strtok(command, " ;");
    assert(c_ptr != NULL);
    c_ptr = strtok(NULL, " ;");
    assert(c_ptr != NULL);
93
    shellSourceFile(c_ptr);
94
    return 0;
H
hzcheng 已提交
95
  }
96

97
  shellRunSingleCommandImp(command);
98
  return 0;
H
hzcheng 已提交
99 100
}

101 102
int32_t shellRunCommand(char *command) {
  if (shellIsEmptyCommand(command)) {
103 104 105
    return 0;
  }

106 107 108 109 110 111
  SShellHistory *pHistory = &shell.history;
  if (pHistory->hstart == pHistory->hend ||
      pHistory->hist[(pHistory->hend + SHELL_MAX_HISTORY_SIZE - 1) % SHELL_MAX_HISTORY_SIZE] == NULL ||
      strcmp(command, pHistory->hist[(pHistory->hend + SHELL_MAX_HISTORY_SIZE - 1) % SHELL_MAX_HISTORY_SIZE]) != 0) {
    if (pHistory->hist[pHistory->hend] != NULL) {
      taosMemoryFreeClear(pHistory->hist[pHistory->hend]);
112
    }
113
    pHistory->hist[pHistory->hend] = strdup(command);
114

115 116 117
    pHistory->hend = (pHistory->hend + 1) % SHELL_MAX_HISTORY_SIZE;
    if (pHistory->hend == pHistory->hstart) {
      pHistory->hstart = (pHistory->hstart + 1) % SHELL_MAX_HISTORY_SIZE;
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
    }
  }

  bool esc = false;
  char quote = 0, *cmd = command, *p = command;
  for (char c = *command++; c != 0; c = *command++) {
    if (esc) {
      switch (c) {
        case 'n':
          c = '\n';
          break;
        case 'r':
          c = '\r';
          break;
        case 't':
          c = '\t';
          break;
        case 'G':
          *p++ = '\\';
          break;
        case '\'':
        case '"':
          if (quote) {
            *p++ = '\\';
          }
          break;
      }
      *p++ = c;
      esc = false;
      continue;
    }
149

150
    if (c == '\\') {
151
      if (quote != 0 && (*command == '_' || *command == '\\')) {
S
Shengliang Guan 已提交
152
        // DO nothing
153 154 155 156
      } else {
        esc = true;
        continue;
      }
157 158 159 160
    }

    if (quote == c) {
      quote = 0;
D
dapan1121 已提交
161
    } else if (quote == 0 && (c == '\'' || c == '"')) {
162 163 164 165
      quote = c;
    }

    *p++ = c;
166
    if (c == ';' && quote == 0) {
167 168
      c = *p;
      *p = 0;
169
      if (shellRunSingleCommand(cmd) < 0) {
170 171 172 173 174 175 176 177
        return -1;
      }
      *p = c;
      p = cmd;
    }
  }

  *p = 0;
178
  return shellRunSingleCommand(cmd);
D
dapan1121 已提交
179 180
}

181
void shellRunSingleCommandImp(char *command) {
182 183 184 185 186
  int64_t st, et;
  char   *sptr = NULL;
  char   *cptr = NULL;
  char   *fname = NULL;
  bool    printMode = false;
H
hzcheng 已提交
187 188 189 190 191 192 193

  if ((sptr = strstr(command, ">>")) != NULL) {
    cptr = strstr(command, ";");
    if (cptr != NULL) {
      *cptr = '\0';
    }

194
    fname = sptr + 2;
H
hzcheng 已提交
195 196 197
    *sptr = '\0';
  }

198 199 200 201 202 203 204 205 206 207
  if ((sptr = strstr(command, "\\G")) != NULL) {
    cptr = strstr(command, ";");
    if (cptr != NULL) {
      *cptr = '\0';
    }

    *sptr = '\0';
    printMode = true;  // When output to a file, the switch does not work.
  }

H
hzcheng 已提交
208 209
  st = taosGetTimestampUs();

210
  TAOS_RES *pSql = taos_query(shell.conn, command);
H
Haojun Liao 已提交
211
  if (taos_errno(pSql)) {
212
    shellPrintError(pSql, st);
H
hzcheng 已提交
213 214 215
    return;
  }

216
  int64_t oresult = atomic_load_64(&shell.result);
D
fix bug  
dapan1121 已提交
217

218
  if (shellRegexMatch(command, "^\\s*use\\s+[a-zA-Z0-9_]+\\s*;\\s*$", REG_EXTENDED | REG_ICASE)) {
H
hzcheng 已提交
219 220
    fprintf(stdout, "Database changed.\n\n");
    fflush(stdout);
221

222
    atomic_store_64(&shell.result, 0);
S
Shengliang Guan 已提交
223 224
    taos_free_result(pSql);

H
hzcheng 已提交
225 226 227
    return;
  }

S
Shengliang Guan 已提交
228
  TAOS_FIELD *pFields = taos_fetch_fields(pSql);
H
Haojun Liao 已提交
229
  if (pFields != NULL) {  // select and show kinds of commands
230
    int32_t error_no = 0;
231

232
    int32_t numOfRows = shellDumpResult(pSql, fname, &error_no, printMode);
H
Haojun Liao 已提交
233
    if (numOfRows < 0) {
234
      atomic_store_64(&shell.result, 0);
H
Haojun Liao 已提交
235 236
      return;
    }
H
hzcheng 已提交
237 238 239 240 241

    et = taosGetTimestampUs();
    if (error_no == 0) {
      printf("Query OK, %d row(s) in set (%.6fs)\n", numOfRows, (et - st) / 1E6);
    } else {
242
      printf("Query interrupted (%s), %d row(s) in set (%.6fs)\n", taos_errstr(pSql), numOfRows, (et - st) / 1E6);
H
hzcheng 已提交
243
    }
S
Shengliang Guan 已提交
244
    taos_free_result(pSql);
H
hzcheng 已提交
245
  } else {
246
    int32_t num_rows_affacted = taos_affected_rows(pSql);
247
    taos_free_result(pSql);
H
hzcheng 已提交
248
    et = taosGetTimestampUs();
D
dapan1121 已提交
249
    printf("Query OK, %d of %d row(s) in database (%.6fs)\n", num_rows_affacted, num_rows_affacted, (et - st) / 1E6);
H
hzcheng 已提交
250 251 252 253
  }

  printf("\n");

254
  atomic_store_64(&shell.result, 0);
H
hzcheng 已提交
255 256
}

257 258
char *shellFormatTimestamp(char *buf, int64_t val, int32_t precision) {
  if (shell.args.is_raw_time) {
259 260 261
    sprintf(buf, "%" PRId64, val);
    return buf;
  }
H
hzcheng 已提交
262

S
Shengliang Guan 已提交
263
  time_t  tt;
D
fix bug  
dapan1121 已提交
264
  int32_t ms = 0;
265 266 267 268
  if (precision == TSDB_TIME_PRECISION_NANO) {
    tt = (time_t)(val / 1000000000);
    ms = val % 1000000000;
  } else if (precision == TSDB_TIME_PRECISION_MICRO) {
269
    tt = (time_t)(val / 1000000);
D
fix bug  
dapan1121 已提交
270
    ms = val % 1000000;
271 272
  } else {
    tt = (time_t)(val / 1000);
D
fix bug  
dapan1121 已提交
273
    ms = val % 1000;
274 275
  }

276 277
  /*
    comment out as it make testcases like select_with_tags.sim fail.
S
Shengliang Guan 已提交
278 279 280 281 282
    but in windows, this may cause the call to localtime crash if tt < 0,
    need to find a better solution.
    if (tt < 0) {
      tt = 0;
    }
283
  */
284

285 286 287
#ifdef WINDOWS
  if (tt < 0) tt = 0;
#endif
S
Shengliang Guan 已提交
288
  if (tt <= 0 && ms < 0) {
D
fix bug  
dapan1121 已提交
289
    tt--;
290 291 292
    if (precision == TSDB_TIME_PRECISION_NANO) {
      ms += 1000000000;
    } else if (precision == TSDB_TIME_PRECISION_MICRO) {
D
fix bug  
dapan1121 已提交
293 294 295 296 297
      ms += 1000000;
    } else {
      ms += 1000;
    }
  }
298

299
  struct tm *ptm = taosLocalTime(&tt, NULL);
S
Shengliang Guan 已提交
300
  size_t     pos = strftime(buf, 35, "%Y-%m-%d %H:%M:%S", ptm);
301

302 303 304
  if (precision == TSDB_TIME_PRECISION_NANO) {
    sprintf(buf + pos, ".%09d", ms);
  } else if (precision == TSDB_TIME_PRECISION_MICRO) {
D
fix bug  
dapan1121 已提交
305
    sprintf(buf + pos, ".%06d", ms);
306
  } else {
D
fix bug  
dapan1121 已提交
307
    sprintf(buf + pos, ".%03d", ms);
308 309 310 311 312
  }

  return buf;
}

313
void shellDumpFieldToFile(TdFilePtr pFile, const char *val, TAOS_FIELD *field, int32_t length, int32_t precision) {
314
  if (val == NULL) {
315
    taosFprintfFile(pFile, "%s", TSDB_DATA_NULL_STR);
316 317 318 319 320 321
    return;
  }

  char buf[TSDB_MAX_BYTES_PER_ROW];
  switch (field->type) {
    case TSDB_DATA_TYPE_BOOL:
322
      taosFprintfFile(pFile, "%d", ((((int32_t)(*((char *)val))) == 1) ? 1 : 0));
323 324
      break;
    case TSDB_DATA_TYPE_TINYINT:
325
      taosFprintfFile(pFile, "%d", *((int8_t *)val));
326 327
      break;
    case TSDB_DATA_TYPE_SMALLINT:
328
      taosFprintfFile(pFile, "%d", *((int16_t *)val));
329 330
      break;
    case TSDB_DATA_TYPE_INT:
331
      taosFprintfFile(pFile, "%d", *((int32_t *)val));
332 333
      break;
    case TSDB_DATA_TYPE_BIGINT:
334
      taosFprintfFile(pFile, "%" PRId64, *((int64_t *)val));
335 336
      break;
    case TSDB_DATA_TYPE_FLOAT:
337
      taosFprintfFile(pFile, "%.5f", GET_FLOAT_VAL(val));
338 339
      break;
    case TSDB_DATA_TYPE_DOUBLE:
340
      taosFprintfFile(pFile, "%.9f", GET_DOUBLE_VAL(val));
341 342 343 344 345
      break;
    case TSDB_DATA_TYPE_BINARY:
    case TSDB_DATA_TYPE_NCHAR:
      memcpy(buf, val, length);
      buf[length] = 0;
346
      taosFprintfFile(pFile, "\'%s\'", buf);
347 348
      break;
    case TSDB_DATA_TYPE_TIMESTAMP:
349
      shellFormatTimestamp(buf, *(int64_t *)val, precision);
350
      taosFprintfFile(pFile, "'%s'", buf);
351 352 353 354 355 356
      break;
    default:
      break;
  }
}

357
int32_t shellDumpResultToFile(const char *fname, TAOS_RES *tres) {
358 359 360 361 362
  char fullname[PATH_MAX] = {0};
  if (taosExpandDir(fname, fullname, PATH_MAX) != 0) {
    tstrncpy(fullname, fname, PATH_MAX);
  }

363
  TAOS_ROW row = taos_fetch_row(tres);
364 365 366 367
  if (row == NULL) {
    return 0;
  }

368
  TdFilePtr pFile = taosOpenFile(fullname, TD_FILE_CREATE | TD_FILE_WRITE | TD_FILE_TRUNC | TD_FILE_STREAM);
369
  if (pFile == NULL) {
370
    fprintf(stderr, "failed to open file: %s\n", fullname);
371 372 373
    return -1;
  }

374
  TAOS_FIELD *fields = taos_fetch_fields(tres);
375 376
  int32_t     num_fields = taos_num_fields(tres);
  int32_t     precision = taos_result_precision(tres);
377

378
  for (int32_t col = 0; col < num_fields; col++) {
379
    if (col > 0) {
380
      taosFprintfFile(pFile, ",");
381
    }
382
    taosFprintfFile(pFile, "%s", fields[col].name);
383
  }
384
  taosFprintfFile(pFile, "\n");
385

386
  int32_t numOfRows = 0;
387
  do {
S
Shengliang Guan 已提交
388
    int32_t *length = taos_fetch_lengths(tres);
389
    for (int32_t i = 0; i < num_fields; i++) {
390
      if (i > 0) {
391
        taosFprintfFile(pFile, "\n");
392
      }
393
      shellDumpFieldToFile(pFile, (const char *)row[i], fields + i, length[i], precision);
H
hzcheng 已提交
394
    }
395
    taosFprintfFile(pFile, "\n");
396 397

    numOfRows++;
398
    row = taos_fetch_row(tres);
S
Shengliang Guan 已提交
399
  } while (row != NULL);
400

401
  atomic_store_64(&shell.result, 0);
402
  taosCloseFile(&pFile);
403

404 405 406
  return numOfRows;
}

407
void shellPrintNChar(const char *str, int32_t length, int32_t width) {
wafwerar's avatar
wafwerar 已提交
408
  TdWchar tail[3];
409
  int32_t pos = 0, cols = 0, totalCols = 0, tailLen = 0;
410

411
  while (pos < length) {
wafwerar's avatar
wafwerar 已提交
412
    TdWchar wc;
413
    int32_t bytes = taosMbToWchar(&wc, str + pos, MB_CUR_MAX);
414 415 416 417 418 419 420 421 422
    if (bytes == 0) {
      break;
    }
    pos += bytes;
    if (pos > length) {
      break;
    }

#ifdef WINDOWS
423
    int32_t w = bytes;
424
#else
425
    int32_t w = taosWcharWidth(wc);
426
#endif
427 428 429 430 431 432 433 434 435 436 437 438 439 440
    if (w <= 0) {
      continue;
    }

    if (width <= 0) {
      printf("%lc", wc);
      continue;
    }

    totalCols += w;
    if (totalCols > width) {
      break;
    }
    if (totalCols <= (width - 3)) {
441 442
      printf("%lc", wc);
      cols += w;
443 444 445
    } else {
      tail[tailLen] = wc;
      tailLen++;
446 447 448
    }
  }

449 450
  if (totalCols > width) {
    // width could be 1 or 2, so printf("...") cannot be used
451
    for (int32_t i = 0; i < 3; i++) {
452 453 454 455 456 457 458
      if (cols >= width) {
        break;
      }
      putchar('.');
      ++cols;
    }
  } else {
459
    for (int32_t i = 0; i < tailLen; i++) {
460 461 462 463 464
      printf("%lc", tail[i]);
    }
    cols = totalCols;
  }

465 466 467 468 469
  for (; cols < width; cols++) {
    putchar(' ');
  }
}

470
void shellPrintField(const char *val, TAOS_FIELD *field, int32_t width, int32_t length, int32_t precision) {
471
  if (val == NULL) {
472
    int32_t w = width;
473 474
    if (field->type < TSDB_DATA_TYPE_TINYINT || field->type > TSDB_DATA_TYPE_DOUBLE) {
      w = 0;
H
hzcheng 已提交
475
    }
476 477 478 479 480 481
    w = printf("%*s", w, TSDB_DATA_NULL_STR);
    for (; w < width; w++) {
      putchar(' ');
    }
    return;
  }
H
hzcheng 已提交
482

483 484 485
  char buf[TSDB_MAX_BYTES_PER_ROW];
  switch (field->type) {
    case TSDB_DATA_TYPE_BOOL:
S
TD-1530  
Shengliang Guan 已提交
486
      printf("%*s", width, ((((int32_t)(*((char *)val))) == 1) ? "true" : "false"));
487 488
      break;
    case TSDB_DATA_TYPE_TINYINT:
S
TD-1530  
Shengliang Guan 已提交
489
      printf("%*d", width, *((int8_t *)val));
490
      break;
491 492 493
    case TSDB_DATA_TYPE_UTINYINT:
      printf("%*u", width, *((uint8_t *)val));
      break;
494
    case TSDB_DATA_TYPE_SMALLINT:
S
TD-1530  
Shengliang Guan 已提交
495
      printf("%*d", width, *((int16_t *)val));
496
      break;
497 498 499
    case TSDB_DATA_TYPE_USMALLINT:
      printf("%*u", width, *((uint16_t *)val));
      break;
500
    case TSDB_DATA_TYPE_INT:
S
TD-1530  
Shengliang Guan 已提交
501
      printf("%*d", width, *((int32_t *)val));
502
      break;
503 504 505
    case TSDB_DATA_TYPE_UINT:
      printf("%*u", width, *((uint32_t *)val));
      break;
506 507 508
    case TSDB_DATA_TYPE_BIGINT:
      printf("%*" PRId64, width, *((int64_t *)val));
      break;
509 510 511
    case TSDB_DATA_TYPE_UBIGINT:
      printf("%*" PRIu64, width, *((uint64_t *)val));
      break;
512 513 514 515 516 517 518 519
    case TSDB_DATA_TYPE_FLOAT:
      printf("%*.5f", width, GET_FLOAT_VAL(val));
      break;
    case TSDB_DATA_TYPE_DOUBLE:
      printf("%*.9f", width, GET_DOUBLE_VAL(val));
      break;
    case TSDB_DATA_TYPE_BINARY:
    case TSDB_DATA_TYPE_NCHAR:
B
Bomin Zhang 已提交
520
      shellPrintNChar(val, length, width);
521 522
      break;
    case TSDB_DATA_TYPE_TIMESTAMP:
523
      shellFormatTimestamp(buf, *(int64_t *)val, precision);
524 525 526 527
      printf("%s", buf);
      break;
    default:
      break;
H
hzcheng 已提交
528
  }
529
}
H
hzcheng 已提交
530

531
int32_t shellVerticalPrintResult(TAOS_RES *tres) {
H
Haojun Liao 已提交
532
  TAOS_ROW row = taos_fetch_row(tres);
533 534 535 536
  if (row == NULL) {
    return 0;
  }

537
  int32_t     num_fields = taos_num_fields(tres);
H
Haojun Liao 已提交
538
  TAOS_FIELD *fields = taos_fetch_fields(tres);
539
  int32_t     precision = taos_result_precision(tres);
540

541 542 543
  int32_t maxColNameLen = 0;
  for (int32_t col = 0; col < num_fields; col++) {
    int32_t len = (int32_t)strlen(fields[col].name);
544 545 546 547 548
    if (len > maxColNameLen) {
      maxColNameLen = len;
    }
  }

D
fix bug  
dapan1121 已提交
549 550
  uint64_t resShowMaxNum = UINT64_MAX;

551
  if (shell.args.commands == NULL && shell.args.file[0] == 0) {
552
    resShowMaxNum = SHELL_DEFAULT_RES_SHOW_NUM;
D
fix bug  
dapan1121 已提交
553 554
  }

555 556
  int32_t numOfRows = 0;
  int32_t showMore = 1;
557
  do {
D
fix bug  
dapan1121 已提交
558
    if (numOfRows < resShowMaxNum) {
D
fix bug  
dapan1121 已提交
559 560
      printf("*************************** %d.row ***************************\n", numOfRows + 1);

S
Shengliang Guan 已提交
561
      int32_t *length = taos_fetch_lengths(tres);
D
fix bug  
dapan1121 已提交
562

563
      for (int32_t i = 0; i < num_fields; i++) {
S
Shengliang Guan 已提交
564
        TAOS_FIELD *field = fields + i;
565

566
        int32_t padding = (int32_t)(maxColNameLen - strlen(field->name));
D
fix bug  
dapan1121 已提交
567
        printf("%*.s%s: ", padding, " ", field->name);
568

569
        shellPrintField((const char *)row[i], field, 0, length[i], precision);
D
fix bug  
dapan1121 已提交
570 571
        putchar('\n');
      }
D
fix bug  
dapan1121 已提交
572
    } else if (showMore) {
S
Shengliang Guan 已提交
573 574 575
      printf("[100 Rows showed, and more rows are fetching but will not be showed. You can ctrl+c to stop or wait.]\n");
      printf("[You can add limit statement to get more or redirect results to specific file to get all.]\n");
      showMore = 0;
576 577 578
    }

    numOfRows++;
H
Haojun Liao 已提交
579
    row = taos_fetch_row(tres);
S
Shengliang Guan 已提交
580
  } while (row != NULL);
581 582 583 584

  return numOfRows;
}

585 586
int32_t shellCalcColWidth(TAOS_FIELD *field, int32_t precision) {
  int32_t width = (int32_t)strlen(field->name);
587 588 589

  switch (field->type) {
    case TSDB_DATA_TYPE_BOOL:
dengyihao's avatar
dengyihao 已提交
590
      return TMAX(5, width);  // 'false'
591 592

    case TSDB_DATA_TYPE_TINYINT:
593
    case TSDB_DATA_TYPE_UTINYINT:
dengyihao's avatar
dengyihao 已提交
594
      return TMAX(4, width);  // '-127'
595 596

    case TSDB_DATA_TYPE_SMALLINT:
597
    case TSDB_DATA_TYPE_USMALLINT:
dengyihao's avatar
dengyihao 已提交
598
      return TMAX(6, width);  // '-32767'
599 600

    case TSDB_DATA_TYPE_INT:
601
    case TSDB_DATA_TYPE_UINT:
dengyihao's avatar
dengyihao 已提交
602
      return TMAX(11, width);  // '-2147483648'
603 604

    case TSDB_DATA_TYPE_BIGINT:
605
    case TSDB_DATA_TYPE_UBIGINT:
dengyihao's avatar
dengyihao 已提交
606
      return TMAX(21, width);  // '-9223372036854775807'
607 608

    case TSDB_DATA_TYPE_FLOAT:
dengyihao's avatar
dengyihao 已提交
609
      return TMAX(20, width);
610 611

    case TSDB_DATA_TYPE_DOUBLE:
dengyihao's avatar
dengyihao 已提交
612
      return TMAX(25, width);
613 614

    case TSDB_DATA_TYPE_BINARY:
615 616
      if (field->bytes > shell.args.displayWidth) {
        return TMAX(shell.args.displayWidth, width);
617
      } else {
dengyihao's avatar
dengyihao 已提交
618
        return TMAX(field->bytes, width);
619 620
      }

621 622
    case TSDB_DATA_TYPE_NCHAR: {
      int16_t bytes = field->bytes * TSDB_NCHAR_SIZE;
623 624
      if (bytes > shell.args.displayWidth) {
        return TMAX(shell.args.displayWidth, width);
625
      } else {
dengyihao's avatar
dengyihao 已提交
626
        return TMAX(bytes, width);
627 628 629
      }
    }

630
    case TSDB_DATA_TYPE_TIMESTAMP:
631
      if (shell.args.is_raw_time) {
dengyihao's avatar
dengyihao 已提交
632
        return TMAX(14, width);
S
Shengliang Guan 已提交
633 634
      }
      if (precision == TSDB_TIME_PRECISION_NANO) {
dengyihao's avatar
dengyihao 已提交
635
        return TMAX(29, width);
636
      } else if (precision == TSDB_TIME_PRECISION_MICRO) {
dengyihao's avatar
dengyihao 已提交
637
        return TMAX(26, width);  // '2020-01-01 00:00:00.000000'
638
      } else {
dengyihao's avatar
dengyihao 已提交
639
        return TMAX(23, width);  // '2020-01-01 00:00:00.000'
S
slguan 已提交
640
      }
H
hzcheng 已提交
641

642 643
    default:
      assert(false);
H
hzcheng 已提交
644 645
  }

646 647
  return 0;
}
H
hzcheng 已提交
648

649 650 651
void shellPrintHeader(TAOS_FIELD *fields, int32_t *width, int32_t num_fields) {
  int32_t rowWidth = 0;
  for (int32_t col = 0; col < num_fields; col++) {
S
Shengliang Guan 已提交
652
    TAOS_FIELD *field = fields + col;
653 654
    int32_t     padding = (int32_t)(width[col] - strlen(field->name));
    int32_t     left = padding / 2;
655 656 657 658 659
    printf(" %*.s%s%*.s |", left, " ", field->name, padding - left, " ");
    rowWidth += width[col] + 3;
  }

  putchar('\n');
660
  for (int32_t i = 0; i < rowWidth; i++) {
661 662 663 664 665
    putchar('=');
  }
  putchar('\n');
}

666
int32_t shellHorizontalPrintResult(TAOS_RES *tres) {
H
Haojun Liao 已提交
667
  TAOS_ROW row = taos_fetch_row(tres);
668 669 670 671
  if (row == NULL) {
    return 0;
  }

672
  int32_t     num_fields = taos_num_fields(tres);
H
Haojun Liao 已提交
673
  TAOS_FIELD *fields = taos_fetch_fields(tres);
674
  int32_t     precision = taos_result_precision(tres);
675

676 677 678
  int32_t width[TSDB_MAX_COLUMNS];
  for (int32_t col = 0; col < num_fields; col++) {
    width[col] = shellCalcColWidth(fields + col, precision);
679 680
  }

681
  shellPrintHeader(fields, width, num_fields);
682

D
fix bug  
dapan1121 已提交
683 684
  uint64_t resShowMaxNum = UINT64_MAX;

685
  if (shell.args.commands == NULL && shell.args.file[0] == 0) {
686
    resShowMaxNum = SHELL_DEFAULT_RES_SHOW_NUM;
D
fix bug  
dapan1121 已提交
687 688
  }

689 690
  int32_t numOfRows = 0;
  int32_t showMore = 1;
691

692
  do {
S
Shengliang Guan 已提交
693
    int32_t *length = taos_fetch_lengths(tres);
D
fix bug  
dapan1121 已提交
694
    if (numOfRows < resShowMaxNum) {
695
      for (int32_t i = 0; i < num_fields; i++) {
D
fix bug  
dapan1121 已提交
696
        putchar(' ');
697
        shellPrintField((const char *)row[i], fields + i, width[i], length[i], precision);
D
fix bug  
dapan1121 已提交
698 699 700 701
        putchar(' ');
        putchar('|');
      }
      putchar('\n');
D
fix bug  
dapan1121 已提交
702
    } else if (showMore) {
S
Shengliang Guan 已提交
703 704 705
      printf("[100 Rows showed, and more rows are fetching but will not be showed. You can ctrl+c to stop or wait.]\n");
      printf("[You can add limit statement to show more or redirect results to specific file to get all.]\n");
      showMore = 0;
706
    }
707

708
    numOfRows++;
H
Haojun Liao 已提交
709
    row = taos_fetch_row(tres);
S
Shengliang Guan 已提交
710
  } while (row != NULL);
711 712 713 714

  return numOfRows;
}

715 716
int32_t shellDumpResult(TAOS_RES *tres, char *fname, int32_t *error_no, bool vertical) {
  int32_t numOfRows = 0;
H
hzcheng 已提交
717
  if (fname != NULL) {
718
    numOfRows = shellDumpResultToFile(fname, tres);
S
Shengliang Guan 已提交
719
  } else if (vertical) {
720
    numOfRows = shellVerticalPrintResult(tres);
721
  } else {
722
    numOfRows = shellHorizontalPrintResult(tres);
H
hzcheng 已提交
723 724
  }

H
Haojun Liao 已提交
725
  *error_no = taos_errno(tres);
H
hzcheng 已提交
726 727 728
  return numOfRows;
}

729
void shellReadHistory() {
730 731 732
  SShellHistory *pHistory = &shell.history;
  TdFilePtr      pFile = taosOpenFile(pHistory->file, TD_FILE_READ | TD_FILE_STREAM);
  if (pFile == NULL) return;
H
hzcheng 已提交
733

734 735
  char   *line = NULL;
  int32_t read_size = 0;
736
  while ((read_size = taosGetLineFile(pFile, &line)) != -1) {
H
hzcheng 已提交
737
    line[read_size - 1] = '\0';
738
    pHistory->hist[pHistory->hend] = strdup(line);
H
hzcheng 已提交
739

740
    pHistory->hend = (pHistory->hend + 1) % SHELL_MAX_HISTORY_SIZE;
H
hzcheng 已提交
741

742 743
    if (pHistory->hend == pHistory->hstart) {
      pHistory->hstart = (pHistory->hstart + 1) % SHELL_MAX_HISTORY_SIZE;
H
hzcheng 已提交
744 745 746
    }
  }

S
Shengliang Guan 已提交
747
  if (line != NULL) taosMemoryFree(line);
748
  taosCloseFile(&pFile);
H
hzcheng 已提交
749 750
}

751
void shellWriteHistory() {
752
  SShellHistory *pHistory = &shell.history;
S
Shengliang Guan 已提交
753
  TdFilePtr      pFile = taosOpenFile(pHistory->file, TD_FILE_CREATE | TD_FILE_WRITE | TD_FILE_STREAM | TD_FILE_APPEND);
754
  if (pFile == NULL) return;
H
hzcheng 已提交
755

756 757 758 759
  for (int32_t i = pHistory->hstart; i != pHistory->hend;) {
    if (pHistory->hist[i] != NULL) {
      taosFprintfFile(pFile, "%s\n", pHistory->hist[i]);
      taosMemoryFreeClear(pHistory->hist[i]);
H
hzcheng 已提交
760
    }
761
    i = (i + 1) % SHELL_MAX_HISTORY_SIZE;
H
hzcheng 已提交
762
  }
763
  taosFsyncFile(pFile);
764
  taosCloseFile(&pFile);
H
hzcheng 已提交
765 766
}

767
void shellPrintError(TAOS_RES *tres, int64_t st) {
S
TD-1793  
Shengliang Guan 已提交
768
  int64_t et = taosGetTimestampUs();
769
  atomic_store_ptr(&shell.result, 0);
S
TD-1793  
Shengliang Guan 已提交
770
  fprintf(stderr, "\nDB error: %s (%.6fs)\n", taos_errstr(tres), (et - st) / 1E6);
H
Haojun Liao 已提交
771
  taos_free_result(tres);
H
hzcheng 已提交
772 773
}

774 775
bool shellIsCommentLine(char *line) {
  if (line == NULL) return true;
776
  return shellRegexMatch(line, "^\\s*#.*", REG_EXTENDED);
H
hzcheng 已提交
777 778
}

779 780 781 782 783
void shellSourceFile(const char *file) {
  int32_t read_len = 0;
  char   *cmd = taosMemoryCalloc(1, TSDB_MAX_ALLOWED_SQL_LEN + 1);
  size_t  cmd_len = 0;
  char   *line = NULL;
784 785 786 787 788
  char    fullname[PATH_MAX] = {0};

  if (taosExpandDir(file, fullname, PATH_MAX) != 0) {
    tstrncpy(fullname, file, PATH_MAX);
  }
H
hzcheng 已提交
789

790
  TdFilePtr pFile = taosOpenFile(fullname, TD_FILE_READ | TD_FILE_STREAM);
791
  if (pFile == NULL) {
792
    fprintf(stderr, "failed to open file %s\n", fullname);
wafwerar's avatar
wafwerar 已提交
793
    taosMemoryFree(cmd);
H
hzcheng 已提交
794 795 796
    return;
  }

797
  while ((read_len = taosGetLineFile(pFile, &line)) != -1) {
H
Haojun Liao 已提交
798
    if (read_len >= TSDB_MAX_ALLOWED_SQL_LEN) continue;
H
hzcheng 已提交
799 800
    line[--read_len] = '\0';

801
    if (read_len == 0 || shellIsCommentLine(line)) {  // line starts with #
H
hzcheng 已提交
802 803 804 805 806 807 808 809 810 811 812
      continue;
    }

    if (line[read_len - 1] == '\\') {
      line[read_len - 1] = ' ';
      memcpy(cmd + cmd_len, line, read_len);
      cmd_len += read_len;
      continue;
    }

    memcpy(cmd + cmd_len, line, read_len);
813 814
    printf("%s%s\n", shell.info.promptHeader, cmd);
    shellRunCommand(cmd);
H
Haojun Liao 已提交
815
    memset(cmd, 0, TSDB_MAX_ALLOWED_SQL_LEN);
H
hzcheng 已提交
816 817 818
    cmd_len = 0;
  }

wafwerar's avatar
wafwerar 已提交
819
  taosMemoryFree(cmd);
S
Shengliang Guan 已提交
820
  if (line != NULL) taosMemoryFree(line);
821
  taosCloseFile(&pFile);
H
hzcheng 已提交
822
}
S
slguan 已提交
823

824
void shellGetGrantInfo() {
825 826 827 828
  char sinfo[1024] = {0};
  tstrncpy(sinfo, taos_get_server_info(shell.conn), sizeof(sinfo));
  strtok(sinfo, "\n");

S
slguan 已提交
829 830
  char sql[] = "show grants";

831
  TAOS_RES *tres = taos_query(shell.conn, sql);
H
Haojun Liao 已提交
832

833
  int32_t code = taos_errno(tres);
S
slguan 已提交
834
  if (code != TSDB_CODE_SUCCESS) {
835
    if (code == TSDB_CODE_OPS_NOT_SUPPORT) {
836
      fprintf(stdout, "Server is Community Edition, %s\n\n", sinfo);
S
slguan 已提交
837
    } else {
838
      fprintf(stderr, "Failed to check Server Edition, Reason:0x%04x:%s\n\n", taos_errno(shell.conn),
839
              taos_errstr(shell.conn));
S
slguan 已提交
840
    }
S
slguan 已提交
841 842 843
    return;
  }

844
  int32_t num_fields = taos_field_count(tres);
S
slguan 已提交
845 846 847 848
  if (num_fields == 0) {
    fprintf(stderr, "\nInvalid grant information.\n");
    exit(0);
  } else {
849
    if (tres == NULL) {
S
slguan 已提交
850 851 852 853
      fprintf(stderr, "\nGrant information is null.\n");
      exit(0);
    }

854
    TAOS_FIELD *fields = taos_fetch_fields(tres);
855
    TAOS_ROW    row = taos_fetch_row(tres);
S
slguan 已提交
856
    if (row == NULL) {
H
hjxilinx 已提交
857
      fprintf(stderr, "\nFailed to get grant information from server. Abort.\n");
S
slguan 已提交
858 859 860
      exit(0);
    }

S
slguan 已提交
861
    char serverVersion[32] = {0};
S
slguan 已提交
862 863 864
    char expiretime[32] = {0};
    char expired[32] = {0};

S
slguan 已提交
865
    memcpy(serverVersion, row[0], fields[0].bytes);
S
slguan 已提交
866 867 868 869
    memcpy(expiretime, row[1], fields[1].bytes);
    memcpy(expired, row[2], fields[2].bytes);

    if (strcmp(expiretime, "unlimited") == 0) {
870
      fprintf(stdout, "Server is Enterprise %s Edition, %s and will never expire.\n", serverVersion, sinfo);
S
slguan 已提交
871
    } else {
872
      fprintf(stdout, "Server is Enterprise %s Edition, %s and will expire at %s.\n", serverVersion, sinfo, expiretime);
S
slguan 已提交
873 874
    }

875
    atomic_store_64(&shell.result, 0);
876
    taos_free_result(tres);
S
slguan 已提交
877 878 879
  }

  fprintf(stdout, "\n");
880 881 882 883
}

void shellQueryInterruptHandler(int32_t signum, void *sigInfo, void *context) { tsem_post(&shell.cancelSem); }

884 885 886 887
void shellSigintHandler(int32_t signum, void *sigInfo, void *context) {
  // do nothing
}

888 889 890 891 892 893 894 895 896 897 898
void shellCleanup(void *arg) { taosResetTerminalMode(); }

void *shellCancelHandler(void *arg) {
  setThreadName("shellCancelHandler");
  while (1) {
    if (tsem_wait(&shell.cancelSem) != 0) {
      taosMsleep(10);
      continue;
    }

    taosResetTerminalMode();
899
    printf("\nReceive SIGTERM or other signal, quit shell.\n");
900 901
    shellWriteHistory();
    shellExit();
902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929
  }

  return NULL;
}

void *shellThreadLoop(void *arg) {
  setThreadName("shellThreadLoop");
  taosGetOldTerminalMode();
  taosThreadCleanupPush(shellCleanup, NULL);

  char *command = taosMemoryMalloc(SHELL_MAX_COMMAND_SIZE);
  if (command == NULL) {
    printf("failed to malloc command\n");
    return NULL;
  }

  do {
    memset(command, 0, SHELL_MAX_COMMAND_SIZE);
    taosSetTerminalMode();

    if (shellReadCommand(command) != 0) {
      break;
    }

    taosResetTerminalMode();
  } while (shellRunCommand(command) == 0);

  taosMemoryFreeClear(command);
930 931
  shellWriteHistory();
  shellExit();
932

933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952
  taosThreadCleanupPop(1);
  return NULL;
}

int32_t shellExecute() {
  printf(shell.info.clientVersion, shell.info.osname, taos_get_client_info());
  fflush(stdout);

  SShellArgs *pArgs = &shell.args;
  if (shell.args.auth == NULL) {
    shell.conn = taos_connect(pArgs->host, pArgs->user, pArgs->password, pArgs->database, pArgs->port);
  } else {
    shell.conn = taos_connect_auth(pArgs->host, pArgs->user, pArgs->auth, pArgs->database, pArgs->port);
  }

  if (shell.conn == NULL) {
    fflush(stdout);
    return -1;
  }

953 954
  shellReadHistory();

955
  if (pArgs->commands != NULL || pArgs->file[0] != 0) {
956 957 958 959 960 961 962
    if (pArgs->commands != NULL) {
      printf("%s%s\n", shell.info.promptHeader, pArgs->commands);
      char *cmd = strdup(pArgs->commands);
      shellRunCommand(cmd);
      taosMemoryFree(cmd);
    }

963
    if (pArgs->file[0] != 0) {
964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983
      shellSourceFile(pArgs->file);
    }

    taos_close(shell.conn);
    shellWriteHistory();
    return 0;
  }

  if (tsem_init(&shell.cancelSem, 0, 0) != 0) {
    printf("failed to create cancel semphore\n");
    return -1;
  }

  TdThread spid = {0};
  taosThreadCreate(&spid, NULL, shellCancelHandler, NULL);

  taosSetSignal(SIGTERM, shellQueryInterruptHandler);
  taosSetSignal(SIGHUP, shellQueryInterruptHandler);
  taosSetSignal(SIGABRT, shellQueryInterruptHandler);

984 985
  taosSetSignal(SIGINT, shellSigintHandler);

986 987 988 989 990 991 992 993
  shellGetGrantInfo(shell.conn);

  while (1) {
    taosThreadCreate(&shell.pid, NULL, shellThreadLoop, shell.conn);
    taosThreadJoin(shell.pid, NULL);
  }

  return 0;
S
slguan 已提交
994
}