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 181 182 183 184
  int64_t st, et;
  char   *sptr = NULL;
  char   *cptr = NULL;
  char   *fname = NULL;
  bool    printMode = false;
H
hzcheng 已提交
185 186 187 188 189 190 191

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

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

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

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

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

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

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

H
hzcheng 已提交
223 224 225
    return;
  }

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

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

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

  printf("\n");

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

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

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

274 275
  /*
    comment out as it make testcases like select_with_tags.sim fail.
S
Shengliang Guan 已提交
276 277 278 279 280
    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;
    }
281
  */
282

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

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

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

  return buf;
}

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

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

355
int32_t shellDumpResultToFile(const char *fname, TAOS_RES *tres) {
356
  TAOS_ROW row = taos_fetch_row(tres);
357 358 359 360
  if (row == NULL) {
    return 0;
  }

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

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

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

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

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

394
  atomic_store_64(&shell.result, 0);
395
  taosCloseFile(&pFile);
396

397 398 399
  return numOfRows;
}

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

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

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

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

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

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

458 459 460 461 462
  for (; cols < width; cols++) {
    putchar(' ');
  }
}

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

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

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

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

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

D
fix bug  
dapan1121 已提交
542 543
  uint64_t resShowMaxNum = UINT64_MAX;

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

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

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

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

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

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

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

  return numOfRows;
}

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

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

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

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

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

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

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

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

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

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

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

635 636
    default:
      assert(false);
H
hzcheng 已提交
637 638
  }

639 640
  return 0;
}
H
hzcheng 已提交
641

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

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

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

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

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

674
  shellPrintHeader(fields, width, num_fields);
675

D
fix bug  
dapan1121 已提交
676 677
  uint64_t resShowMaxNum = UINT64_MAX;

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

682 683
  int32_t numOfRows = 0;
  int32_t showMore = 1;
684

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

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

  return numOfRows;
}

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

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

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

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

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

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

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

744
void shellWriteHistory() {
745 746 747
  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 已提交
748

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

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

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

771 772 773 774 775
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 已提交
776

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

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

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

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

811
void shellGetGrantInfo() {
S
slguan 已提交
812 813
  char sql[] = "show grants";

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

816
  int32_t code = taos_errno(tres);
S
slguan 已提交
817
  if (code != TSDB_CODE_SUCCESS) {
818 819
    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 已提交
820
    } else {
821 822
      fprintf(stderr, "Failed to check Server Edition, Reason:%d:%s\n\n", taos_errno(shell.conn),
              taos_errstr(shell.conn));
S
slguan 已提交
823
    }
S
slguan 已提交
824 825 826
    return;
  }

827
  int32_t num_fields = taos_field_count(tres);
S
slguan 已提交
828 829 830 831
  if (num_fields == 0) {
    fprintf(stderr, "\nInvalid grant information.\n");
    exit(0);
  } else {
832
    if (tres == NULL) {
S
slguan 已提交
833 834 835 836
      fprintf(stderr, "\nGrant information is null.\n");
      exit(0);
    }

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

S
slguan 已提交
844
    char serverVersion[32] = {0};
S
slguan 已提交
845 846 847
    char expiretime[32] = {0};
    char expired[32] = {0};

S
slguan 已提交
848
    memcpy(serverVersion, row[0], fields[0].bytes);
S
slguan 已提交
849 850 851 852
    memcpy(expiretime, row[1], fields[1].bytes);
    memcpy(expired, row[2], fields[2].bytes);

    if (strcmp(expiretime, "unlimited") == 0) {
853 854
      fprintf(stdout, "Server is Enterprise %s Edition, version is %s and will never expire.\n", serverVersion,
              taos_get_server_info(shell.conn));
S
slguan 已提交
855
    } else {
856 857
      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 已提交
858 859
    }

860
    atomic_store_64(&shell.result, 0);
861
    taos_free_result(tres);
S
slguan 已提交
862 863 864
  }

  fprintf(stdout, "\n");
865 866 867 868 869 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
}

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);
910 911
  // shellExit();

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
  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 已提交
971
}