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

S
Shengliang Guan 已提交
220 221
    taos_free_result(pSql);

H
hzcheng 已提交
222 223 224
    return;
  }

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

229
    int32_t numOfRows = shellDumpResult(pSql, fname, &error_no, printMode);
230
    if (numOfRows < 0) return;
H
hzcheng 已提交
231 232 233 234 235

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

  printf("\n");
}

249 250
char *shellFormatTimestamp(char *buf, int64_t val, int32_t precision) {
  if (shell.args.is_raw_time) {
251 252 253
    sprintf(buf, "%" PRId64, val);
    return buf;
  }
H
hzcheng 已提交
254

S
Shengliang Guan 已提交
255
  time_t  tt;
D
fix bug  
dapan1121 已提交
256
  int32_t ms = 0;
257 258 259 260
  if (precision == TSDB_TIME_PRECISION_NANO) {
    tt = (time_t)(val / 1000000000);
    ms = val % 1000000000;
  } else if (precision == TSDB_TIME_PRECISION_MICRO) {
261
    tt = (time_t)(val / 1000000);
D
fix bug  
dapan1121 已提交
262
    ms = val % 1000000;
263 264
  } else {
    tt = (time_t)(val / 1000);
D
fix bug  
dapan1121 已提交
265
    ms = val % 1000;
266 267
  }

268 269
  /*
    comment out as it make testcases like select_with_tags.sim fail.
S
Shengliang Guan 已提交
270 271 272 273 274
    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;
    }
275
  */
276

277 278 279
#ifdef WINDOWS
  if (tt < 0) tt = 0;
#endif
S
Shengliang Guan 已提交
280
  if (tt <= 0 && ms < 0) {
D
fix bug  
dapan1121 已提交
281
    tt--;
282 283 284
    if (precision == TSDB_TIME_PRECISION_NANO) {
      ms += 1000000000;
    } else if (precision == TSDB_TIME_PRECISION_MICRO) {
D
fix bug  
dapan1121 已提交
285 286 287 288 289
      ms += 1000000;
    } else {
      ms += 1000;
    }
  }
290

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

294 295 296
  if (precision == TSDB_TIME_PRECISION_NANO) {
    sprintf(buf + pos, ".%09d", ms);
  } else if (precision == TSDB_TIME_PRECISION_MICRO) {
D
fix bug  
dapan1121 已提交
297
    sprintf(buf + pos, ".%06d", ms);
298
  } else {
D
fix bug  
dapan1121 已提交
299
    sprintf(buf + pos, ".%03d", ms);
300 301 302 303 304
  }

  return buf;
}

305
void shellDumpFieldToFile(TdFilePtr pFile, const char *val, TAOS_FIELD *field, int32_t length, int32_t precision) {
306
  if (val == NULL) {
307
    taosFprintfFile(pFile, "%s", TSDB_DATA_NULL_STR);
308 309 310 311 312 313
    return;
  }

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

349
int32_t shellDumpResultToFile(const char *fname, TAOS_RES *tres) {
350 351 352 353 354
  char fullname[PATH_MAX] = {0};
  if (taosExpandDir(fname, fullname, PATH_MAX) != 0) {
    tstrncpy(fullname, fname, PATH_MAX);
  }

355
  TAOS_ROW row = taos_fetch_row(tres);
356 357 358 359
  if (row == NULL) {
    return 0;
  }

360
  TdFilePtr pFile = taosOpenFile(fullname, TD_FILE_CREATE | TD_FILE_WRITE | TD_FILE_TRUNC | TD_FILE_STREAM);
361
  if (pFile == NULL) {
362
    fprintf(stderr, "failed to open file: %s\n", fullname);
363 364 365
    return -1;
  }

366
  TAOS_FIELD *fields = taos_fetch_fields(tres);
367 368
  int32_t     num_fields = taos_num_fields(tres);
  int32_t     precision = taos_result_precision(tres);
369

370
  for (int32_t col = 0; col < num_fields; col++) {
371
    if (col > 0) {
372
      taosFprintfFile(pFile, ",");
373
    }
374
    taosFprintfFile(pFile, "%s", fields[col].name);
375
  }
376
  taosFprintfFile(pFile, "\n");
377

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

    numOfRows++;
390
    row = taos_fetch_row(tres);
S
Shengliang Guan 已提交
391
  } while (row != NULL);
392

393
  taosCloseFile(&pFile);
394

395 396 397
  return numOfRows;
}

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

402
  while (pos < length) {
wafwerar's avatar
wafwerar 已提交
403
    TdWchar wc;
404
    int32_t bytes = taosMbToWchar(&wc, str + pos, MB_CUR_MAX);
405 406 407 408 409 410 411 412 413
    if (bytes == 0) {
      break;
    }
    pos += bytes;
    if (pos > length) {
      break;
    }

#ifdef WINDOWS
414
    int32_t w = bytes;
415
#else
416
    int32_t w = taosWcharWidth(wc);
417
#endif
418 419 420 421 422 423 424 425 426 427 428 429 430 431
    if (w <= 0) {
      continue;
    }

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

    totalCols += w;
    if (totalCols > width) {
      break;
    }
    if (totalCols <= (width - 3)) {
432 433
      printf("%lc", wc);
      cols += w;
434 435 436
    } else {
      tail[tailLen] = wc;
      tailLen++;
437 438 439
    }
  }

440 441
  if (totalCols > width) {
    // width could be 1 or 2, so printf("...") cannot be used
442
    for (int32_t i = 0; i < 3; i++) {
443 444 445 446 447 448 449
      if (cols >= width) {
        break;
      }
      putchar('.');
      ++cols;
    }
  } else {
450
    for (int32_t i = 0; i < tailLen; i++) {
451 452 453 454 455
      printf("%lc", tail[i]);
    }
    cols = totalCols;
  }

456 457 458 459 460
  for (; cols < width; cols++) {
    putchar(' ');
  }
}

461
void shellPrintField(const char *val, TAOS_FIELD *field, int32_t width, int32_t length, int32_t precision) {
462
  if (val == NULL) {
463
    int32_t w = width;
464 465
    if (field->type < TSDB_DATA_TYPE_TINYINT || field->type > TSDB_DATA_TYPE_DOUBLE) {
      w = 0;
H
hzcheng 已提交
466
    }
467 468 469 470 471 472
    w = printf("%*s", w, TSDB_DATA_NULL_STR);
    for (; w < width; w++) {
      putchar(' ');
    }
    return;
  }
H
hzcheng 已提交
473

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

522
int32_t shellVerticalPrintResult(TAOS_RES *tres) {
H
Haojun Liao 已提交
523
  TAOS_ROW row = taos_fetch_row(tres);
524 525 526 527
  if (row == NULL) {
    return 0;
  }

528
  int32_t     num_fields = taos_num_fields(tres);
H
Haojun Liao 已提交
529
  TAOS_FIELD *fields = taos_fetch_fields(tres);
530
  int32_t     precision = taos_result_precision(tres);
531

532 533 534
  int32_t maxColNameLen = 0;
  for (int32_t col = 0; col < num_fields; col++) {
    int32_t len = (int32_t)strlen(fields[col].name);
535 536 537 538 539
    if (len > maxColNameLen) {
      maxColNameLen = len;
    }
  }

D
fix bug  
dapan1121 已提交
540 541
  uint64_t resShowMaxNum = UINT64_MAX;

542
  if (shell.args.commands == NULL && shell.args.file[0] == 0) {
543
    resShowMaxNum = SHELL_DEFAULT_RES_SHOW_NUM;
D
fix bug  
dapan1121 已提交
544 545
  }

546 547
  int32_t numOfRows = 0;
  int32_t showMore = 1;
548
  do {
D
fix bug  
dapan1121 已提交
549
    if (numOfRows < resShowMaxNum) {
D
fix bug  
dapan1121 已提交
550 551
      printf("*************************** %d.row ***************************\n", numOfRows + 1);

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

554
      for (int32_t i = 0; i < num_fields; i++) {
S
Shengliang Guan 已提交
555
        TAOS_FIELD *field = fields + i;
556

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

560
        shellPrintField((const char *)row[i], field, 0, length[i], precision);
D
fix bug  
dapan1121 已提交
561 562
        putchar('\n');
      }
D
fix bug  
dapan1121 已提交
563
    } else if (showMore) {
S
Shengliang Guan 已提交
564 565 566
      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;
567 568 569
    }

    numOfRows++;
H
Haojun Liao 已提交
570
    row = taos_fetch_row(tres);
S
Shengliang Guan 已提交
571
  } while (row != NULL);
572 573 574 575

  return numOfRows;
}

576 577
int32_t shellCalcColWidth(TAOS_FIELD *field, int32_t precision) {
  int32_t width = (int32_t)strlen(field->name);
578 579 580

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

    case TSDB_DATA_TYPE_TINYINT:
584
    case TSDB_DATA_TYPE_UTINYINT:
dengyihao's avatar
dengyihao 已提交
585
      return TMAX(4, width);  // '-127'
586 587

    case TSDB_DATA_TYPE_SMALLINT:
588
    case TSDB_DATA_TYPE_USMALLINT:
dengyihao's avatar
dengyihao 已提交
589
      return TMAX(6, width);  // '-32767'
590 591

    case TSDB_DATA_TYPE_INT:
592
    case TSDB_DATA_TYPE_UINT:
dengyihao's avatar
dengyihao 已提交
593
      return TMAX(11, width);  // '-2147483648'
594 595

    case TSDB_DATA_TYPE_BIGINT:
596
    case TSDB_DATA_TYPE_UBIGINT:
dengyihao's avatar
dengyihao 已提交
597
      return TMAX(21, width);  // '-9223372036854775807'
598 599

    case TSDB_DATA_TYPE_FLOAT:
dengyihao's avatar
dengyihao 已提交
600
      return TMAX(20, width);
601 602

    case TSDB_DATA_TYPE_DOUBLE:
dengyihao's avatar
dengyihao 已提交
603
      return TMAX(25, width);
604 605

    case TSDB_DATA_TYPE_BINARY:
606 607
      if (field->bytes > shell.args.displayWidth) {
        return TMAX(shell.args.displayWidth, width);
608
      } else {
dengyihao's avatar
dengyihao 已提交
609
        return TMAX(field->bytes, width);
610 611
      }

612 613
    case TSDB_DATA_TYPE_NCHAR: {
      int16_t bytes = field->bytes * TSDB_NCHAR_SIZE;
614 615
      if (bytes > shell.args.displayWidth) {
        return TMAX(shell.args.displayWidth, width);
616
      } else {
dengyihao's avatar
dengyihao 已提交
617
        return TMAX(bytes, width);
618 619 620
      }
    }

621
    case TSDB_DATA_TYPE_TIMESTAMP:
622
      if (shell.args.is_raw_time) {
dengyihao's avatar
dengyihao 已提交
623
        return TMAX(14, width);
S
Shengliang Guan 已提交
624 625
      }
      if (precision == TSDB_TIME_PRECISION_NANO) {
dengyihao's avatar
dengyihao 已提交
626
        return TMAX(29, width);
627
      } else if (precision == TSDB_TIME_PRECISION_MICRO) {
dengyihao's avatar
dengyihao 已提交
628
        return TMAX(26, width);  // '2020-01-01 00:00:00.000000'
629
      } else {
dengyihao's avatar
dengyihao 已提交
630
        return TMAX(23, width);  // '2020-01-01 00:00:00.000'
S
slguan 已提交
631
      }
H
hzcheng 已提交
632

633 634
    default:
      assert(false);
H
hzcheng 已提交
635 636
  }

637 638
  return 0;
}
H
hzcheng 已提交
639

640 641 642
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 已提交
643
    TAOS_FIELD *field = fields + col;
644 645
    int32_t     padding = (int32_t)(width[col] - strlen(field->name));
    int32_t     left = padding / 2;
646 647 648 649 650
    printf(" %*.s%s%*.s |", left, " ", field->name, padding - left, " ");
    rowWidth += width[col] + 3;
  }

  putchar('\n');
651
  for (int32_t i = 0; i < rowWidth; i++) {
652 653 654 655 656
    putchar('=');
  }
  putchar('\n');
}

657
int32_t shellHorizontalPrintResult(TAOS_RES *tres) {
H
Haojun Liao 已提交
658
  TAOS_ROW row = taos_fetch_row(tres);
659 660 661 662
  if (row == NULL) {
    return 0;
  }

663
  int32_t     num_fields = taos_num_fields(tres);
H
Haojun Liao 已提交
664
  TAOS_FIELD *fields = taos_fetch_fields(tres);
665
  int32_t     precision = taos_result_precision(tres);
666

667 668 669
  int32_t width[TSDB_MAX_COLUMNS];
  for (int32_t col = 0; col < num_fields; col++) {
    width[col] = shellCalcColWidth(fields + col, precision);
670 671
  }

672
  shellPrintHeader(fields, width, num_fields);
673

D
fix bug  
dapan1121 已提交
674 675
  uint64_t resShowMaxNum = UINT64_MAX;

676
  if (shell.args.commands == NULL && shell.args.file[0] == 0) {
677
    resShowMaxNum = SHELL_DEFAULT_RES_SHOW_NUM;
D
fix bug  
dapan1121 已提交
678 679
  }

680 681
  int32_t numOfRows = 0;
  int32_t showMore = 1;
682

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

699
    numOfRows++;
H
Haojun Liao 已提交
700
    row = taos_fetch_row(tres);
S
Shengliang Guan 已提交
701
  } while (row != NULL);
702 703 704 705

  return numOfRows;
}

706 707
int32_t shellDumpResult(TAOS_RES *tres, char *fname, int32_t *error_no, bool vertical) {
  int32_t numOfRows = 0;
H
hzcheng 已提交
708
  if (fname != NULL) {
709
    numOfRows = shellDumpResultToFile(fname, tres);
S
Shengliang Guan 已提交
710
  } else if (vertical) {
711
    numOfRows = shellVerticalPrintResult(tres);
712
  } else {
713
    numOfRows = shellHorizontalPrintResult(tres);
H
hzcheng 已提交
714 715
  }

H
Haojun Liao 已提交
716
  *error_no = taos_errno(tres);
H
hzcheng 已提交
717 718 719
  return numOfRows;
}

720
void shellReadHistory() {
721 722 723
  SShellHistory *pHistory = &shell.history;
  TdFilePtr      pFile = taosOpenFile(pHistory->file, TD_FILE_READ | TD_FILE_STREAM);
  if (pFile == NULL) return;
H
hzcheng 已提交
724

725 726
  char   *line = NULL;
  int32_t read_size = 0;
727
  while ((read_size = taosGetLineFile(pFile, &line)) != -1) {
H
hzcheng 已提交
728
    line[read_size - 1] = '\0';
729
    pHistory->hist[pHistory->hend] = strdup(line);
H
hzcheng 已提交
730

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

733 734
    if (pHistory->hend == pHistory->hstart) {
      pHistory->hstart = (pHistory->hstart + 1) % SHELL_MAX_HISTORY_SIZE;
H
hzcheng 已提交
735 736 737
    }
  }

S
Shengliang Guan 已提交
738
  if (line != NULL) taosMemoryFree(line);
739
  taosCloseFile(&pFile);
H
hzcheng 已提交
740 741
}

742
void shellWriteHistory() {
743
  SShellHistory *pHistory = &shell.history;
S
Shengliang Guan 已提交
744
  TdFilePtr      pFile = taosOpenFile(pHistory->file, TD_FILE_CREATE | TD_FILE_WRITE | TD_FILE_STREAM | TD_FILE_APPEND);
745
  if (pFile == NULL) return;
H
hzcheng 已提交
746

747 748 749 750
  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 已提交
751
    }
752
    i = (i + 1) % SHELL_MAX_HISTORY_SIZE;
H
hzcheng 已提交
753
  }
754
  taosFsyncFile(pFile);
755
  taosCloseFile(&pFile);
H
hzcheng 已提交
756 757
}

758
void shellPrintError(TAOS_RES *tres, int64_t st) {
S
TD-1793  
Shengliang Guan 已提交
759 760
  int64_t et = taosGetTimestampUs();
  fprintf(stderr, "\nDB error: %s (%.6fs)\n", taos_errstr(tres), (et - st) / 1E6);
H
Haojun Liao 已提交
761
  taos_free_result(tres);
H
hzcheng 已提交
762 763
}

764 765
bool shellIsCommentLine(char *line) {
  if (line == NULL) return true;
766
  return shellRegexMatch(line, "^\\s*#.*", REG_EXTENDED);
H
hzcheng 已提交
767 768
}

769 770 771 772 773
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;
774 775 776 777 778
  char    fullname[PATH_MAX] = {0};

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

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

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

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

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

814
void shellGetGrantInfo() {
815 816 817 818
  char sinfo[1024] = {0};
  tstrncpy(sinfo, taos_get_server_info(shell.conn), sizeof(sinfo));
  strtok(sinfo, "\n");

S
slguan 已提交
819 820
  char sql[] = "show grants";

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

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

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

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

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

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

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

865
    taos_free_result(tres);
S
slguan 已提交
866 867 868
  }

  fprintf(stdout, "\n");
869 870 871 872
}

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

873 874 875 876
void shellSigintHandler(int32_t signum, void *sigInfo, void *context) {
  // do nothing
}

877 878 879 880 881 882 883 884 885 886 887
void shellCleanup(void *arg) { taosResetTerminalMode(); }

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

    taosResetTerminalMode();
888
    printf("\nReceive SIGTERM or other signal, quit shell.\n");
889 890
    shellWriteHistory();
    shellExit();
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
  }

  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);
919 920
  shellWriteHistory();
  shellExit();
921

922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941
  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;
  }

942 943
  shellReadHistory();

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

952
    if (pArgs->file[0] != 0) {
953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972
      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);

973 974
  taosSetSignal(SIGINT, shellSigintHandler);

975 976 977 978 979 980 981 982
  shellGetGrantInfo(shell.conn);

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

  return 0;
S
slguan 已提交
983
}