shellEngine.c 26.2 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 (strcasecmp(p, "default") == 0) {
78
      shell.args.displayWidth = SHELL_DEFAULT_MAX_BINARY_DISPLAY_WIDTH;
79
    } else {
80
      shell.args.displayWidth = atoi(p);
81
    }
82 83
    return 0;
  }
84

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

95
  shellRunSingleCommandImp(command);
96
  return 0;
H
hzcheng 已提交
97 98
}

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

104 105 106 107 108 109
  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]);
110
    }
111
    pHistory->hist[pHistory->hend] = strdup(command);
112

113 114 115
    pHistory->hend = (pHistory->hend + 1) % SHELL_MAX_HISTORY_SIZE;
    if (pHistory->hend == pHistory->hstart) {
      pHistory->hstart = (pHistory->hstart + 1) % SHELL_MAX_HISTORY_SIZE;
116 117 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
    }
  }

  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;
    }
147

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

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

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

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

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

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

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

197 198 199 200 201 202 203 204 205 206
  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 已提交
207 208
  st = taosGetTimestampUs();

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

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

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

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

H
hzcheng 已提交
224 225 226
    return;
  }

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

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

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

  printf("\n");

  if (fname != NULL) {
    wordfree(&full_path);
  }
256

257
  atomic_store_64(&shell.result, 0);
H
hzcheng 已提交
258 259
}

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

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

279 280
  /*
    comment out as it make testcases like select_with_tags.sim fail.
S
Shengliang Guan 已提交
281 282 283 284 285
    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;
    }
286
  */
287

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

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

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

  return buf;
}

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

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

360
int32_t shellDumpResultToFile(const char *fname, TAOS_RES *tres) {
361
  TAOS_ROW row = taos_fetch_row(tres);
362 363 364 365
  if (row == NULL) {
    return 0;
  }

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

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

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

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

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

399
  shell.result = 0;
400
  taosCloseFile(&pFile);
401

402 403 404
  return numOfRows;
}

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

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

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

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

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

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

463 464 465 466 467
  for (; cols < width; cols++) {
    putchar(' ');
  }
}

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

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

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

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

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

D
fix bug  
dapan1121 已提交
547 548
  uint64_t resShowMaxNum = UINT64_MAX;

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

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

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

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

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

567
        shellPrintField((const char *)row[i], field, 0, length[i], precision);
D
fix bug  
dapan1121 已提交
568 569
        putchar('\n');
      }
D
fix bug  
dapan1121 已提交
570
    } else if (showMore) {
S
Shengliang Guan 已提交
571 572 573
      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;
574 575 576
    }

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

  return numOfRows;
}

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

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

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

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

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

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

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

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

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

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

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

640 641
    default:
      assert(false);
H
hzcheng 已提交
642 643
  }

644 645
  return 0;
}
H
hzcheng 已提交
646

647 648 649
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 已提交
650
    TAOS_FIELD *field = fields + col;
651 652
    int32_t     padding = (int32_t)(width[col] - strlen(field->name));
    int32_t     left = padding / 2;
653 654 655 656 657
    printf(" %*.s%s%*.s |", left, " ", field->name, padding - left, " ");
    rowWidth += width[col] + 3;
  }

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

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

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

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

679
  shellPrintHeader(fields, width, num_fields);
680

D
fix bug  
dapan1121 已提交
681 682
  uint64_t resShowMaxNum = UINT64_MAX;

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

687 688
  int32_t numOfRows = 0;
  int32_t showMore = 1;
689

690
  do {
S
Shengliang Guan 已提交
691
    int32_t *length = taos_fetch_lengths(tres);
D
fix bug  
dapan1121 已提交
692
    if (numOfRows < resShowMaxNum) {
693
      for (int32_t i = 0; i < num_fields; i++) {
D
fix bug  
dapan1121 已提交
694
        putchar(' ');
695
        shellPrintField((const char *)row[i], fields + i, width[i], length[i], precision);
D
fix bug  
dapan1121 已提交
696 697 698 699
        putchar(' ');
        putchar('|');
      }
      putchar('\n');
D
fix bug  
dapan1121 已提交
700
    } else if (showMore) {
S
Shengliang Guan 已提交
701 702 703
      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;
704
    }
705

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

  return numOfRows;
}

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

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

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

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

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

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

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

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

754 755 756 757
  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 已提交
758
    }
759
    i = (i + 1) % SHELL_MAX_HISTORY_SIZE;
H
hzcheng 已提交
760
  }
761
  taosCloseFile(&pFile);
H
hzcheng 已提交
762 763
}

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

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

776 777 778 779 780
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;
H
hzcheng 已提交
781

782
  TdFilePtr pFile = taosOpenFile(file, TD_FILE_READ | TD_FILE_STREAM);
783
  if (pFile == NULL) {
784
    fprintf(stderr, "ERROR: failed to open file %s\n", file);
wafwerar's avatar
wafwerar 已提交
785
    taosMemoryFree(cmd);
H
hzcheng 已提交
786 787 788
    return;
  }

789
  while ((read_len = taosGetLineFile(pFile, &line)) != -1) {
H
Haojun Liao 已提交
790
    if (read_len >= TSDB_MAX_ALLOWED_SQL_LEN) continue;
H
hzcheng 已提交
791 792
    line[--read_len] = '\0';

793
    if (read_len == 0 || shellIsCommentLine(line)) {  // line starts with #
H
hzcheng 已提交
794 795 796 797 798 799 800 801 802 803 804
      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);
805 806
    printf("%s%s\n", shell.info.promptHeader, cmd);
    shellRunCommand(cmd);
H
Haojun Liao 已提交
807
    memset(cmd, 0, TSDB_MAX_ALLOWED_SQL_LEN);
H
hzcheng 已提交
808 809 810
    cmd_len = 0;
  }

wafwerar's avatar
wafwerar 已提交
811
  taosMemoryFree(cmd);
S
Shengliang Guan 已提交
812
  if (line != NULL) taosMemoryFree(line);
813
  taosCloseFile(&pFile);
H
hzcheng 已提交
814
}
S
slguan 已提交
815

816
void shellGetGrantInfo() {
S
slguan 已提交
817 818
  char sql[] = "show grants";

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

821
  int32_t code = taos_errno(tres);
S
slguan 已提交
822
  if (code != TSDB_CODE_SUCCESS) {
823 824
    if (code == TSDB_CODE_OPS_NOT_SUPPORT) {
      fprintf(stdout, "Server is Community Edition, version is %s\n\n", taos_get_server_info(shell.conn));
S
slguan 已提交
825
    } else {
826 827
      fprintf(stderr, "Failed to check Server Edition, Reason:%d:%s\n\n", taos_errno(shell.conn),
              taos_errstr(shell.conn));
S
slguan 已提交
828
    }
S
slguan 已提交
829 830 831
    return;
  }

832
  int32_t num_fields = taos_field_count(tres);
S
slguan 已提交
833 834 835 836
  if (num_fields == 0) {
    fprintf(stderr, "\nInvalid grant information.\n");
    exit(0);
  } else {
837
    if (tres == NULL) {
S
slguan 已提交
838 839 840 841
      fprintf(stderr, "\nGrant information is null.\n");
      exit(0);
    }

842
    TAOS_FIELD *fields = taos_fetch_fields(tres);
843
    TAOS_ROW    row = taos_fetch_row(tres);
S
slguan 已提交
844
    if (row == NULL) {
H
hjxilinx 已提交
845
      fprintf(stderr, "\nFailed to get grant information from server. Abort.\n");
S
slguan 已提交
846 847 848
      exit(0);
    }

S
slguan 已提交
849
    char serverVersion[32] = {0};
S
slguan 已提交
850 851 852
    char expiretime[32] = {0};
    char expired[32] = {0};

S
slguan 已提交
853
    memcpy(serverVersion, row[0], fields[0].bytes);
S
slguan 已提交
854 855 856 857
    memcpy(expiretime, row[1], fields[1].bytes);
    memcpy(expired, row[2], fields[2].bytes);

    if (strcmp(expiretime, "unlimited") == 0) {
858 859
      fprintf(stdout, "Server is Enterprise %s Edition, version is %s and will never expire.\n", serverVersion,
              taos_get_server_info(shell.conn));
S
slguan 已提交
860
    } else {
861 862
      fprintf(stdout, "Server is Enterprise %s Edition, version is %s and will expire at %s.\n", serverVersion,
              taos_get_server_info(shell.conn), expiretime);
S
slguan 已提交
863 864
    }

865
    shell.result = 0;
866
    taos_free_result(tres);
S
slguan 已提交
867 868 869
  }

  fprintf(stdout, "\n");
870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 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 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973
}

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

void shellCleanup(void *arg) { taosResetTerminalMode(); }

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

    taosResetTerminalMode();
    printf("\nReceive ctrl+c or other signal, quit shell.\n");
    // shellExit();
  }

  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);
  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;
  }

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

    if (pArgs->file[0] != 0) {
      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(SIGINT, shellQueryInterruptHandler);
  taosSetSignal(SIGHUP, shellQueryInterruptHandler);
  taosSetSignal(SIGABRT, shellQueryInterruptHandler);

  shellGetGrantInfo(shell.conn);
  shellReadHistory();

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

  return 0;
S
slguan 已提交
974
}