shellEngine.c 25.9 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
    }
  }

wmmhello's avatar
wmmhello 已提交
121
  char quote = 0, *cmd = command;
122
  for (char c = *command++; c != 0; c = *command++) {
wmmhello's avatar
wmmhello 已提交
123 124
    if (c == '\\' && (*command == '\'' || *command == '"' || *command == '`')) {
      command ++;
125 126
      continue;
    }
127

128 129
    if (quote == c) {
      quote = 0;
wmmhello's avatar
wmmhello 已提交
130
    } else if (quote == 0 && (c == '\'' || c == '"' || c == '`')) {
131
      quote = c;
wmmhello's avatar
wmmhello 已提交
132 133 134
    } else if (c == ';' && quote == 0) {
      c = *command;
      *command = 0;
135
      if (shellRunSingleCommand(cmd) < 0) {
136 137
        return -1;
      }
wmmhello's avatar
wmmhello 已提交
138 139
      *command = c;
      cmd = command;
140 141
    }
  }
142
  return shellRunSingleCommand(cmd);
D
dapan1121 已提交
143 144
}

145
void shellRunSingleCommandImp(char *command) {
146 147 148 149 150
  int64_t st, et;
  char   *sptr = NULL;
  char   *cptr = NULL;
  char   *fname = NULL;
  bool    printMode = false;
H
hzcheng 已提交
151 152 153 154 155 156 157

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

158
    fname = sptr + 2;
H
hzcheng 已提交
159 160 161
    *sptr = '\0';
  }

162 163 164 165 166 167 168 169 170 171
  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 已提交
172 173
  st = taosGetTimestampUs();

174
  TAOS_RES *pSql = taos_query(shell.conn, command);
H
Haojun Liao 已提交
175
  if (taos_errno(pSql)) {
176
    shellPrintError(pSql, st);
H
hzcheng 已提交
177 178 179
    return;
  }

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

S
Shengliang Guan 已提交
184 185
    taos_free_result(pSql);

H
hzcheng 已提交
186 187 188
    return;
  }

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

193
    int32_t numOfRows = shellDumpResult(pSql, fname, &error_no, printMode);
194
    if (numOfRows < 0) return;
H
hzcheng 已提交
195 196 197 198 199

    et = taosGetTimestampUs();
    if (error_no == 0) {
      printf("Query OK, %d row(s) in set (%.6fs)\n", numOfRows, (et - st) / 1E6);
    } else {
200
      printf("Query interrupted (%s), %d row(s) in set (%.6fs)\n", taos_errstr(pSql), numOfRows, (et - st) / 1E6);
H
hzcheng 已提交
201
    }
S
Shengliang Guan 已提交
202
    taos_free_result(pSql);
H
hzcheng 已提交
203
  } else {
204
    int32_t num_rows_affacted = taos_affected_rows(pSql);
205
    taos_free_result(pSql);
H
hzcheng 已提交
206
    et = taosGetTimestampUs();
D
dapan1121 已提交
207
    printf("Query OK, %d of %d row(s) in database (%.6fs)\n", num_rows_affacted, num_rows_affacted, (et - st) / 1E6);
H
hzcheng 已提交
208 209 210 211 212
  }

  printf("\n");
}

213 214
char *shellFormatTimestamp(char *buf, int64_t val, int32_t precision) {
  if (shell.args.is_raw_time) {
215 216 217
    sprintf(buf, "%" PRId64, val);
    return buf;
  }
H
hzcheng 已提交
218

S
Shengliang Guan 已提交
219
  time_t  tt;
D
fix bug  
dapan1121 已提交
220
  int32_t ms = 0;
221 222 223 224
  if (precision == TSDB_TIME_PRECISION_NANO) {
    tt = (time_t)(val / 1000000000);
    ms = val % 1000000000;
  } else if (precision == TSDB_TIME_PRECISION_MICRO) {
225
    tt = (time_t)(val / 1000000);
D
fix bug  
dapan1121 已提交
226
    ms = val % 1000000;
227 228
  } else {
    tt = (time_t)(val / 1000);
D
fix bug  
dapan1121 已提交
229
    ms = val % 1000;
230 231
  }

232 233
  /*
    comment out as it make testcases like select_with_tags.sim fail.
S
Shengliang Guan 已提交
234 235 236 237 238
    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;
    }
239
  */
240

241 242 243
#ifdef WINDOWS
  if (tt < 0) tt = 0;
#endif
S
Shengliang Guan 已提交
244
  if (tt <= 0 && ms < 0) {
D
fix bug  
dapan1121 已提交
245
    tt--;
246 247 248
    if (precision == TSDB_TIME_PRECISION_NANO) {
      ms += 1000000000;
    } else if (precision == TSDB_TIME_PRECISION_MICRO) {
D
fix bug  
dapan1121 已提交
249 250 251 252 253
      ms += 1000000;
    } else {
      ms += 1000;
    }
  }
254

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

258 259 260
  if (precision == TSDB_TIME_PRECISION_NANO) {
    sprintf(buf + pos, ".%09d", ms);
  } else if (precision == TSDB_TIME_PRECISION_MICRO) {
D
fix bug  
dapan1121 已提交
261
    sprintf(buf + pos, ".%06d", ms);
262
  } else {
D
fix bug  
dapan1121 已提交
263
    sprintf(buf + pos, ".%03d", ms);
264 265 266 267 268
  }

  return buf;
}

269
void shellDumpFieldToFile(TdFilePtr pFile, const char *val, TAOS_FIELD *field, int32_t length, int32_t precision) {
270
  if (val == NULL) {
271
    taosFprintfFile(pFile, "%s", TSDB_DATA_NULL_STR);
272 273 274 275 276 277
    return;
  }

  char buf[TSDB_MAX_BYTES_PER_ROW];
  switch (field->type) {
    case TSDB_DATA_TYPE_BOOL:
278
      taosFprintfFile(pFile, "%d", ((((int32_t)(*((char *)val))) == 1) ? 1 : 0));
279 280
      break;
    case TSDB_DATA_TYPE_TINYINT:
281
      taosFprintfFile(pFile, "%d", *((int8_t *)val));
282 283
      break;
    case TSDB_DATA_TYPE_SMALLINT:
284
      taosFprintfFile(pFile, "%d", *((int16_t *)val));
285 286
      break;
    case TSDB_DATA_TYPE_INT:
287
      taosFprintfFile(pFile, "%d", *((int32_t *)val));
288 289
      break;
    case TSDB_DATA_TYPE_BIGINT:
290
      taosFprintfFile(pFile, "%" PRId64, *((int64_t *)val));
291 292
      break;
    case TSDB_DATA_TYPE_FLOAT:
293
      taosFprintfFile(pFile, "%.5f", GET_FLOAT_VAL(val));
294 295
      break;
    case TSDB_DATA_TYPE_DOUBLE:
296
      taosFprintfFile(pFile, "%.9f", GET_DOUBLE_VAL(val));
297 298 299 300 301
      break;
    case TSDB_DATA_TYPE_BINARY:
    case TSDB_DATA_TYPE_NCHAR:
      memcpy(buf, val, length);
      buf[length] = 0;
302
      taosFprintfFile(pFile, "\'%s\'", buf);
303 304
      break;
    case TSDB_DATA_TYPE_TIMESTAMP:
305
      shellFormatTimestamp(buf, *(int64_t *)val, precision);
306
      taosFprintfFile(pFile, "'%s'", buf);
307 308 309 310 311 312
      break;
    default:
      break;
  }
}

313
int32_t shellDumpResultToFile(const char *fname, TAOS_RES *tres) {
314 315 316 317 318
  char fullname[PATH_MAX] = {0};
  if (taosExpandDir(fname, fullname, PATH_MAX) != 0) {
    tstrncpy(fullname, fname, PATH_MAX);
  }

319
  TAOS_ROW row = taos_fetch_row(tres);
320 321 322 323
  if (row == NULL) {
    return 0;
  }

324
  TdFilePtr pFile = taosOpenFile(fullname, TD_FILE_CREATE | TD_FILE_WRITE | TD_FILE_TRUNC | TD_FILE_STREAM);
325
  if (pFile == NULL) {
326
    fprintf(stderr, "failed to open file: %s\n", fullname);
327 328 329
    return -1;
  }

330
  TAOS_FIELD *fields = taos_fetch_fields(tres);
331 332
  int32_t     num_fields = taos_num_fields(tres);
  int32_t     precision = taos_result_precision(tres);
333

334
  for (int32_t col = 0; col < num_fields; col++) {
335
    if (col > 0) {
336
      taosFprintfFile(pFile, ",");
337
    }
338
    taosFprintfFile(pFile, "%s", fields[col].name);
339
  }
340
  taosFprintfFile(pFile, "\n");
341

342
  int32_t numOfRows = 0;
343
  do {
S
Shengliang Guan 已提交
344
    int32_t *length = taos_fetch_lengths(tres);
345
    for (int32_t i = 0; i < num_fields; i++) {
346
      if (i > 0) {
347
        taosFprintfFile(pFile, "\n");
348
      }
349
      shellDumpFieldToFile(pFile, (const char *)row[i], fields + i, length[i], precision);
H
hzcheng 已提交
350
    }
351
    taosFprintfFile(pFile, "\n");
352 353

    numOfRows++;
354
    row = taos_fetch_row(tres);
S
Shengliang Guan 已提交
355
  } while (row != NULL);
356

357
  taosCloseFile(&pFile);
358

359 360 361
  return numOfRows;
}

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

366
  while (pos < length) {
wafwerar's avatar
wafwerar 已提交
367
    TdWchar wc;
368
    int32_t bytes = taosMbToWchar(&wc, str + pos, MB_CUR_MAX);
369 370 371 372 373 374 375 376 377
    if (bytes == 0) {
      break;
    }
    pos += bytes;
    if (pos > length) {
      break;
    }

#ifdef WINDOWS
378
    int32_t w = bytes;
379
#else
380
    int32_t w = taosWcharWidth(wc);
381
#endif
382 383 384 385 386 387 388 389 390 391 392 393 394 395
    if (w <= 0) {
      continue;
    }

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

    totalCols += w;
    if (totalCols > width) {
      break;
    }
    if (totalCols <= (width - 3)) {
396 397
      printf("%lc", wc);
      cols += w;
398 399 400
    } else {
      tail[tailLen] = wc;
      tailLen++;
401 402 403
    }
  }

404 405
  if (totalCols > width) {
    // width could be 1 or 2, so printf("...") cannot be used
406
    for (int32_t i = 0; i < 3; i++) {
407 408 409 410 411 412 413
      if (cols >= width) {
        break;
      }
      putchar('.');
      ++cols;
    }
  } else {
414
    for (int32_t i = 0; i < tailLen; i++) {
415 416 417 418 419
      printf("%lc", tail[i]);
    }
    cols = totalCols;
  }

420 421 422 423 424
  for (; cols < width; cols++) {
    putchar(' ');
  }
}

425
void shellPrintField(const char *val, TAOS_FIELD *field, int32_t width, int32_t length, int32_t precision) {
426
  if (val == NULL) {
427
    int32_t w = width;
428 429
    if (field->type < TSDB_DATA_TYPE_TINYINT || field->type > TSDB_DATA_TYPE_DOUBLE) {
      w = 0;
H
hzcheng 已提交
430
    }
431 432 433 434 435 436
    w = printf("%*s", w, TSDB_DATA_NULL_STR);
    for (; w < width; w++) {
      putchar(' ');
    }
    return;
  }
H
hzcheng 已提交
437

438 439 440
  char buf[TSDB_MAX_BYTES_PER_ROW];
  switch (field->type) {
    case TSDB_DATA_TYPE_BOOL:
S
TD-1530  
Shengliang Guan 已提交
441
      printf("%*s", width, ((((int32_t)(*((char *)val))) == 1) ? "true" : "false"));
442 443
      break;
    case TSDB_DATA_TYPE_TINYINT:
S
TD-1530  
Shengliang Guan 已提交
444
      printf("%*d", width, *((int8_t *)val));
445
      break;
446 447 448
    case TSDB_DATA_TYPE_UTINYINT:
      printf("%*u", width, *((uint8_t *)val));
      break;
449
    case TSDB_DATA_TYPE_SMALLINT:
S
TD-1530  
Shengliang Guan 已提交
450
      printf("%*d", width, *((int16_t *)val));
451
      break;
452 453 454
    case TSDB_DATA_TYPE_USMALLINT:
      printf("%*u", width, *((uint16_t *)val));
      break;
455
    case TSDB_DATA_TYPE_INT:
S
TD-1530  
Shengliang Guan 已提交
456
      printf("%*d", width, *((int32_t *)val));
457
      break;
458 459 460
    case TSDB_DATA_TYPE_UINT:
      printf("%*u", width, *((uint32_t *)val));
      break;
461 462 463
    case TSDB_DATA_TYPE_BIGINT:
      printf("%*" PRId64, width, *((int64_t *)val));
      break;
464 465 466
    case TSDB_DATA_TYPE_UBIGINT:
      printf("%*" PRIu64, width, *((uint64_t *)val));
      break;
467 468 469 470 471 472 473 474
    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 已提交
475
      shellPrintNChar(val, length, width);
476 477
      break;
    case TSDB_DATA_TYPE_TIMESTAMP:
478
      shellFormatTimestamp(buf, *(int64_t *)val, precision);
479 480 481 482
      printf("%s", buf);
      break;
    default:
      break;
H
hzcheng 已提交
483
  }
484
}
H
hzcheng 已提交
485

486
int32_t shellVerticalPrintResult(TAOS_RES *tres) {
H
Haojun Liao 已提交
487
  TAOS_ROW row = taos_fetch_row(tres);
488 489 490 491
  if (row == NULL) {
    return 0;
  }

492
  int32_t     num_fields = taos_num_fields(tres);
H
Haojun Liao 已提交
493
  TAOS_FIELD *fields = taos_fetch_fields(tres);
494
  int32_t     precision = taos_result_precision(tres);
495

496 497 498
  int32_t maxColNameLen = 0;
  for (int32_t col = 0; col < num_fields; col++) {
    int32_t len = (int32_t)strlen(fields[col].name);
499 500 501 502 503
    if (len > maxColNameLen) {
      maxColNameLen = len;
    }
  }

D
fix bug  
dapan1121 已提交
504 505
  uint64_t resShowMaxNum = UINT64_MAX;

506
  if (shell.args.commands == NULL && shell.args.file[0] == 0) {
507
    resShowMaxNum = SHELL_DEFAULT_RES_SHOW_NUM;
D
fix bug  
dapan1121 已提交
508 509
  }

510 511
  int32_t numOfRows = 0;
  int32_t showMore = 1;
512
  do {
D
fix bug  
dapan1121 已提交
513
    if (numOfRows < resShowMaxNum) {
D
fix bug  
dapan1121 已提交
514 515
      printf("*************************** %d.row ***************************\n", numOfRows + 1);

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

518
      for (int32_t i = 0; i < num_fields; i++) {
S
Shengliang Guan 已提交
519
        TAOS_FIELD *field = fields + i;
520

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

524
        shellPrintField((const char *)row[i], field, 0, length[i], precision);
D
fix bug  
dapan1121 已提交
525 526
        putchar('\n');
      }
D
fix bug  
dapan1121 已提交
527
    } else if (showMore) {
S
Shengliang Guan 已提交
528 529 530
      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;
531 532 533
    }

    numOfRows++;
H
Haojun Liao 已提交
534
    row = taos_fetch_row(tres);
S
Shengliang Guan 已提交
535
  } while (row != NULL);
536 537 538 539

  return numOfRows;
}

540 541
int32_t shellCalcColWidth(TAOS_FIELD *field, int32_t precision) {
  int32_t width = (int32_t)strlen(field->name);
542 543 544

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

    case TSDB_DATA_TYPE_TINYINT:
548
    case TSDB_DATA_TYPE_UTINYINT:
dengyihao's avatar
dengyihao 已提交
549
      return TMAX(4, width);  // '-127'
550 551

    case TSDB_DATA_TYPE_SMALLINT:
552
    case TSDB_DATA_TYPE_USMALLINT:
dengyihao's avatar
dengyihao 已提交
553
      return TMAX(6, width);  // '-32767'
554 555

    case TSDB_DATA_TYPE_INT:
556
    case TSDB_DATA_TYPE_UINT:
dengyihao's avatar
dengyihao 已提交
557
      return TMAX(11, width);  // '-2147483648'
558 559

    case TSDB_DATA_TYPE_BIGINT:
560
    case TSDB_DATA_TYPE_UBIGINT:
dengyihao's avatar
dengyihao 已提交
561
      return TMAX(21, width);  // '-9223372036854775807'
562 563

    case TSDB_DATA_TYPE_FLOAT:
dengyihao's avatar
dengyihao 已提交
564
      return TMAX(20, width);
565 566

    case TSDB_DATA_TYPE_DOUBLE:
dengyihao's avatar
dengyihao 已提交
567
      return TMAX(25, width);
568 569

    case TSDB_DATA_TYPE_BINARY:
570 571
      if (field->bytes > shell.args.displayWidth) {
        return TMAX(shell.args.displayWidth, width);
572
      } else {
dengyihao's avatar
dengyihao 已提交
573
        return TMAX(field->bytes, width);
574 575
      }

576 577
    case TSDB_DATA_TYPE_NCHAR: {
      int16_t bytes = field->bytes * TSDB_NCHAR_SIZE;
578 579
      if (bytes > shell.args.displayWidth) {
        return TMAX(shell.args.displayWidth, width);
580
      } else {
dengyihao's avatar
dengyihao 已提交
581
        return TMAX(bytes, width);
582 583 584
      }
    }

585
    case TSDB_DATA_TYPE_TIMESTAMP:
586
      if (shell.args.is_raw_time) {
dengyihao's avatar
dengyihao 已提交
587
        return TMAX(14, width);
S
Shengliang Guan 已提交
588 589
      }
      if (precision == TSDB_TIME_PRECISION_NANO) {
dengyihao's avatar
dengyihao 已提交
590
        return TMAX(29, width);
591
      } else if (precision == TSDB_TIME_PRECISION_MICRO) {
dengyihao's avatar
dengyihao 已提交
592
        return TMAX(26, width);  // '2020-01-01 00:00:00.000000'
593
      } else {
dengyihao's avatar
dengyihao 已提交
594
        return TMAX(23, width);  // '2020-01-01 00:00:00.000'
S
slguan 已提交
595
      }
H
hzcheng 已提交
596

597 598
    default:
      assert(false);
H
hzcheng 已提交
599 600
  }

601 602
  return 0;
}
H
hzcheng 已提交
603

604 605 606
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 已提交
607
    TAOS_FIELD *field = fields + col;
608 609
    int32_t     padding = (int32_t)(width[col] - strlen(field->name));
    int32_t     left = padding / 2;
610 611 612 613 614
    printf(" %*.s%s%*.s |", left, " ", field->name, padding - left, " ");
    rowWidth += width[col] + 3;
  }

  putchar('\n');
615
  for (int32_t i = 0; i < rowWidth; i++) {
616 617 618 619 620
    putchar('=');
  }
  putchar('\n');
}

621
int32_t shellHorizontalPrintResult(TAOS_RES *tres) {
H
Haojun Liao 已提交
622
  TAOS_ROW row = taos_fetch_row(tres);
623 624 625 626
  if (row == NULL) {
    return 0;
  }

627
  int32_t     num_fields = taos_num_fields(tres);
H
Haojun Liao 已提交
628
  TAOS_FIELD *fields = taos_fetch_fields(tres);
629
  int32_t     precision = taos_result_precision(tres);
630

631 632 633
  int32_t width[TSDB_MAX_COLUMNS];
  for (int32_t col = 0; col < num_fields; col++) {
    width[col] = shellCalcColWidth(fields + col, precision);
634 635
  }

636
  shellPrintHeader(fields, width, num_fields);
637

D
fix bug  
dapan1121 已提交
638 639
  uint64_t resShowMaxNum = UINT64_MAX;

640
  if (shell.args.commands == NULL && shell.args.file[0] == 0) {
641
    resShowMaxNum = SHELL_DEFAULT_RES_SHOW_NUM;
D
fix bug  
dapan1121 已提交
642 643
  }

644 645
  int32_t numOfRows = 0;
  int32_t showMore = 1;
646

647
  do {
S
Shengliang Guan 已提交
648
    int32_t *length = taos_fetch_lengths(tres);
D
fix bug  
dapan1121 已提交
649
    if (numOfRows < resShowMaxNum) {
650
      for (int32_t i = 0; i < num_fields; i++) {
D
fix bug  
dapan1121 已提交
651
        putchar(' ');
652
        shellPrintField((const char *)row[i], fields + i, width[i], length[i], precision);
D
fix bug  
dapan1121 已提交
653 654 655 656
        putchar(' ');
        putchar('|');
      }
      putchar('\n');
D
fix bug  
dapan1121 已提交
657
    } else if (showMore) {
S
Shengliang Guan 已提交
658 659 660
      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;
661
    }
662

663
    numOfRows++;
H
Haojun Liao 已提交
664
    row = taos_fetch_row(tres);
S
Shengliang Guan 已提交
665
  } while (row != NULL);
666 667 668 669

  return numOfRows;
}

670 671
int32_t shellDumpResult(TAOS_RES *tres, char *fname, int32_t *error_no, bool vertical) {
  int32_t numOfRows = 0;
H
hzcheng 已提交
672
  if (fname != NULL) {
673
    numOfRows = shellDumpResultToFile(fname, tres);
S
Shengliang Guan 已提交
674
  } else if (vertical) {
675
    numOfRows = shellVerticalPrintResult(tres);
676
  } else {
677
    numOfRows = shellHorizontalPrintResult(tres);
H
hzcheng 已提交
678 679
  }

H
Haojun Liao 已提交
680
  *error_no = taos_errno(tres);
H
hzcheng 已提交
681 682 683
  return numOfRows;
}

684
void shellReadHistory() {
685 686 687
  SShellHistory *pHistory = &shell.history;
  TdFilePtr      pFile = taosOpenFile(pHistory->file, TD_FILE_READ | TD_FILE_STREAM);
  if (pFile == NULL) return;
H
hzcheng 已提交
688

689 690
  char   *line = NULL;
  int32_t read_size = 0;
691
  while ((read_size = taosGetLineFile(pFile, &line)) != -1) {
H
hzcheng 已提交
692
    line[read_size - 1] = '\0';
693
    pHistory->hist[pHistory->hend] = strdup(line);
H
hzcheng 已提交
694

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

697 698
    if (pHistory->hend == pHistory->hstart) {
      pHistory->hstart = (pHistory->hstart + 1) % SHELL_MAX_HISTORY_SIZE;
H
hzcheng 已提交
699 700 701
    }
  }

S
Shengliang Guan 已提交
702
  if (line != NULL) taosMemoryFree(line);
703
  taosCloseFile(&pFile);
H
hzcheng 已提交
704 705
}

706
void shellWriteHistory() {
707
  SShellHistory *pHistory = &shell.history;
S
Shengliang Guan 已提交
708
  TdFilePtr      pFile = taosOpenFile(pHistory->file, TD_FILE_CREATE | TD_FILE_WRITE | TD_FILE_STREAM | TD_FILE_APPEND);
709
  if (pFile == NULL) return;
H
hzcheng 已提交
710

711 712 713 714
  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 已提交
715
    }
716
    i = (i + 1) % SHELL_MAX_HISTORY_SIZE;
H
hzcheng 已提交
717
  }
718
  taosFsyncFile(pFile);
719
  taosCloseFile(&pFile);
H
hzcheng 已提交
720 721
}

722
void shellPrintError(TAOS_RES *tres, int64_t st) {
S
TD-1793  
Shengliang Guan 已提交
723 724
  int64_t et = taosGetTimestampUs();
  fprintf(stderr, "\nDB error: %s (%.6fs)\n", taos_errstr(tres), (et - st) / 1E6);
H
Haojun Liao 已提交
725
  taos_free_result(tres);
H
hzcheng 已提交
726 727
}

728 729
bool shellIsCommentLine(char *line) {
  if (line == NULL) return true;
730
  return shellRegexMatch(line, "^\\s*#.*", REG_EXTENDED);
H
hzcheng 已提交
731 732
}

733 734 735 736 737
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;
738
  char    fullname[PATH_MAX] = {0};
H
hzcheng 已提交
739

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

744
  TdFilePtr pFile = taosOpenFile(fullname, TD_FILE_READ | TD_FILE_STREAM);
745
  if (pFile == NULL) {
746
    fprintf(stderr, "failed to open file %s\n", fullname);
wafwerar's avatar
wafwerar 已提交
747
    taosMemoryFree(cmd);
H
hzcheng 已提交
748 749 750
    return;
  }

751
  while ((read_len = taosGetLineFile(pFile, &line)) != -1) {
H
Haojun Liao 已提交
752
    if (read_len >= TSDB_MAX_ALLOWED_SQL_LEN) continue;
H
hzcheng 已提交
753 754
    line[--read_len] = '\0';

755
    if (read_len == 0 || shellIsCommentLine(line)) {  // line starts with #
H
hzcheng 已提交
756 757 758 759 760 761 762 763 764 765 766
      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);
767 768
    printf("%s%s\n", shell.info.promptHeader, cmd);
    shellRunCommand(cmd);
H
Haojun Liao 已提交
769
    memset(cmd, 0, TSDB_MAX_ALLOWED_SQL_LEN);
H
hzcheng 已提交
770 771 772
    cmd_len = 0;
  }

wafwerar's avatar
wafwerar 已提交
773
  taosMemoryFree(cmd);
S
Shengliang Guan 已提交
774
  if (line != NULL) taosMemoryFree(line);
775
  taosCloseFile(&pFile);
H
hzcheng 已提交
776
}
S
slguan 已提交
777

778
void shellGetGrantInfo() {
779 780 781 782
  char sinfo[1024] = {0};
  tstrncpy(sinfo, taos_get_server_info(shell.conn), sizeof(sinfo));
  strtok(sinfo, "\n");

S
slguan 已提交
783 784
  char sql[] = "show grants";

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

787
  int32_t code = taos_errno(tres);
S
slguan 已提交
788
  if (code != TSDB_CODE_SUCCESS) {
789
    if (code == TSDB_CODE_OPS_NOT_SUPPORT) {
790
      fprintf(stdout, "Server is Community Edition, %s\n\n", sinfo);
S
slguan 已提交
791
    } else {
792
      fprintf(stderr, "Failed to check Server Edition, Reason:0x%04x:%s\n\n", taos_errno(shell.conn),
793
              taos_errstr(shell.conn));
S
slguan 已提交
794
    }
S
slguan 已提交
795 796 797
    return;
  }

798
  int32_t num_fields = taos_field_count(tres);
S
slguan 已提交
799 800 801 802
  if (num_fields == 0) {
    fprintf(stderr, "\nInvalid grant information.\n");
    exit(0);
  } else {
803
    if (tres == NULL) {
S
slguan 已提交
804 805 806 807
      fprintf(stderr, "\nGrant information is null.\n");
      exit(0);
    }

808
    TAOS_FIELD *fields = taos_fetch_fields(tres);
809
    TAOS_ROW    row = taos_fetch_row(tres);
S
slguan 已提交
810
    if (row == NULL) {
H
hjxilinx 已提交
811
      fprintf(stderr, "\nFailed to get grant information from server. Abort.\n");
S
slguan 已提交
812 813 814
      exit(0);
    }

S
slguan 已提交
815
    char serverVersion[32] = {0};
S
slguan 已提交
816 817 818
    char expiretime[32] = {0};
    char expired[32] = {0};

S
slguan 已提交
819
    memcpy(serverVersion, row[0], fields[0].bytes);
S
slguan 已提交
820 821 822 823
    memcpy(expiretime, row[1], fields[1].bytes);
    memcpy(expired, row[2], fields[2].bytes);

    if (strcmp(expiretime, "unlimited") == 0) {
824
      fprintf(stdout, "Server is Enterprise %s Edition, %s and will never expire.\n", serverVersion, sinfo);
S
slguan 已提交
825
    } else {
826
      fprintf(stdout, "Server is Enterprise %s Edition, %s and will expire at %s.\n", serverVersion, sinfo, expiretime);
S
slguan 已提交
827 828
    }

829
    taos_free_result(tres);
S
slguan 已提交
830 831 832
  }

  fprintf(stdout, "\n");
833 834 835 836
}

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

837 838 839 840
void shellSigintHandler(int32_t signum, void *sigInfo, void *context) {
  // do nothing
}

841 842 843 844 845 846 847 848 849 850 851
void shellCleanup(void *arg) { taosResetTerminalMode(); }

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

    taosResetTerminalMode();
852
    printf("\nReceive SIGTERM or other signal, quit shell.\n");
853 854
    shellWriteHistory();
    shellExit();
855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882
  }

  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);
883 884
  shellWriteHistory();
  shellExit();
885

886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905
  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;
  }

906 907
  shellReadHistory();

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

916
    if (pArgs->file[0] != 0) {
917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936
      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);

937 938
  taosSetSignal(SIGINT, shellSigintHandler);

939 940 941 942 943 944 945 946
  shellGetGrantInfo(shell.conn);

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

  return 0;
S
slguan 已提交
947
}