|
14 | 14 | #include "mainloop.h" |
15 | 15 | #include "mb/pg_wchar.h" |
16 | 16 | #include "prompt.h" |
| 17 | +#include "psqlplus.h" |
17 | 18 | #include "settings.h" |
| 19 | +#include "stringutils.h" |
| 20 | +#include "variables.h" |
18 | 21 | #include "oracle_fe_utils/ora_string_utils.h" |
19 | 22 | #include "oracle_fe_utils/ora_psqlscan.h" |
20 | 23 | #include "fe_utils/psqlscan_int.h" |
@@ -428,6 +431,251 @@ MainLoop(FILE *source) |
428 | 431 | ora_psql_scan_setup(ora_scan_state, line, strlen(line), |
429 | 432 | pset.encoding, standard_strings()); |
430 | 433 | } |
| 434 | + /* |
| 435 | + * At present, compatible-oracle client commands are all single-line |
| 436 | + * commands, so we only scan the input of stmt_lineno = 1, which can |
| 437 | + * avoid the performance loss caused by the parser invoked by multi-line |
| 438 | + * commands. |
| 439 | + * |
| 440 | + * Note: |
| 441 | + * Oracle client command also extends over multiple lines, but |
| 442 | + * must using the SQL*Plus command continuation character(eg: -), |
| 443 | + * the ontinuation character we haven't implemented it yet, so for |
| 444 | + * the time being we're still assuming that these client commands |
| 445 | + * are all one-liners. |
| 446 | + */ |
| 447 | + if (db_mode == DB_ORACLE && pset.stmt_lineno == 1) |
| 448 | + { |
| 449 | + PsqlScanState pstate; |
| 450 | + yyscan_t yyscanner; |
| 451 | + char *psqlplus_line = pg_strdup(line);; |
| 452 | + |
| 453 | + pstate = ora_psql_scan_create(&psqlplus_callbacks); |
| 454 | + ora_psql_scan_setup(pstate, psqlplus_line, |
| 455 | + strlen(psqlplus_line), |
| 456 | + pset.encoding, |
| 457 | + standard_strings()); |
| 458 | + |
| 459 | + yyscanner = psqlplus_scanner_init(pstate); |
| 460 | + if (psqlplus_yyparse(yyscanner) == 0) |
| 461 | + { |
| 462 | + /* Parser success, i.e. this is a ora client command */ |
| 463 | + psqlplus_cmd *psqlpluscmd = pstate->psqlpluscmd; |
| 464 | + |
| 465 | + switch(psqlpluscmd->cmd_type) |
| 466 | + { |
| 467 | + case PSQLPLUS_CMD_VARIABLE: |
| 468 | + { |
| 469 | + psqlplus_cmd_var *bind_var = (psqlplus_cmd_var *) pstate->psqlpluscmd; |
| 470 | + |
| 471 | + if (bind_var->list_bind_var == true) |
| 472 | + { |
| 473 | + if (bind_var->var_name) |
| 474 | + ListBindVariables(pset.vars, bind_var->var_name); |
| 475 | + else |
| 476 | + ListBindVariables(pset.vars, NULL); |
| 477 | + } |
| 478 | + else if (bind_var->miss_termination_quote) |
| 479 | + { |
| 480 | + /* |
| 481 | + * init_value[0] indicates whether this is a single quote |
| 482 | + * or a double quote that is missing a terminating quote. |
| 483 | + */ |
| 484 | + char quote = bind_var->init_value[0]; |
| 485 | + char *str_double_quote = pg_malloc0(strlen(bind_var->init_value) * 2); /* enough */ |
| 486 | + char *ptr = str_double_quote; |
| 487 | + int i; |
| 488 | + |
| 489 | + /* skip first char */ |
| 490 | + *ptr++ = bind_var->init_value[0]; |
| 491 | + i = 1; |
| 492 | + |
| 493 | + while (bind_var->init_value[i] != '\0') |
| 494 | + { |
| 495 | + /* double write quote if needed */ |
| 496 | + if (bind_var->init_value[i] == quote) |
| 497 | + *ptr++ = bind_var->init_value[i]; |
| 498 | + |
| 499 | + /* copy the character */ |
| 500 | + *ptr++ = bind_var->init_value[i]; |
| 501 | + i++; |
| 502 | + } |
| 503 | + |
| 504 | + *ptr = '\0'; /* Paranoid */ |
| 505 | + |
| 506 | + /* report an error directly */ |
| 507 | + pg_log_error("string \"%s\" missing terminating quote (%c).", |
| 508 | + str_double_quote , |
| 509 | + quote); |
| 510 | + pg_free(str_double_quote); |
| 511 | + } |
| 512 | + else if (bind_var->assign_bind_var) |
| 513 | + { |
| 514 | + AssignBindVariable(pset.vars, |
| 515 | + bind_var->var_name, |
| 516 | + bind_var->init_value); |
| 517 | + } |
| 518 | + else |
| 519 | + { |
| 520 | + Assert(bind_var->vartype); |
| 521 | + SetBindVariable(pset.vars, |
| 522 | + bind_var->var_name, |
| 523 | + bind_var->vartype->oid, |
| 524 | + bind_var->vartype->typmod, |
| 525 | + bind_var->init_value, |
| 526 | + bind_var->initial_nonnull_value); |
| 527 | + } |
| 528 | + } |
| 529 | + break; |
| 530 | + case PSQLPLUS_CMD_PRINT: |
| 531 | + { |
| 532 | + psqlplus_cmd_print *pb = (psqlplus_cmd_print *) pstate->psqlpluscmd; |
| 533 | + PrintBindVariables(pset.vars, pb->print_items); |
| 534 | + } |
| 535 | + break; |
| 536 | + default: |
| 537 | + pg_log_error("Invalid PSQL*PLUS client command."); |
| 538 | + break; |
| 539 | + } |
| 540 | + |
| 541 | + /* save client command in history */ |
| 542 | + if (pset.cur_cmd_interactive) |
| 543 | + { |
| 544 | + pg_append_history(psqlplus_line, history_buf); |
| 545 | + pg_send_history(history_buf); |
| 546 | + } |
| 547 | + |
| 548 | + /* reset */ |
| 549 | + pset.stmt_lineno = 1; |
| 550 | + resetPQExpBuffer(query_buf); |
| 551 | + psql_scan_finish(scan_state); |
| 552 | + ora_psql_scan_finish(ora_scan_state); |
| 553 | + free(psqlplus_line); |
| 554 | + free(line); |
| 555 | + psqlplus_scanner_finish(yyscanner); |
| 556 | + ora_psql_scan_destroy(pstate); |
| 557 | + continue; |
| 558 | + } |
| 559 | + |
| 560 | + /* Syntax parsing failed, but we know it's a client command */ |
| 561 | + if (pstate->is_sqlplus_cmd) |
| 562 | + { |
| 563 | + char *token; |
| 564 | + const char *whitespace = " \t\n\r"; |
| 565 | + |
| 566 | + token = strtokx(pstate->scanline, whitespace, NULL, NULL, |
| 567 | + 0, false, false, pset.encoding); |
| 568 | + |
| 569 | + if (token && pg_strcasecmp(token, "variable") == 0) |
| 570 | + { |
| 571 | + token = strtokx(NULL, whitespace, NULL, NULL, |
| 572 | + 0, false, false, pset.encoding); |
| 573 | + |
| 574 | + /* |
| 575 | + * keep in sync with 'truncate_char' in psqlplusparse.y |
| 576 | + */ |
| 577 | + if (token) |
| 578 | + token[strcspn(token, ",()")] = '\0'; |
| 579 | + |
| 580 | + /* |
| 581 | + * Theoretically, this token is the name of a VARIABLE variable. |
| 582 | + * Check whether the reason for the parsing failure is that the |
| 583 | + * name is illegal. |
| 584 | + */ |
| 585 | + if (token && !ValidBindVariableName(token)) |
| 586 | + pg_log_error("Illegal variable name \"%s\"", token); |
| 587 | + else |
| 588 | + pg_log_error("Usage: VAR[IABLE] [ <variable> [ NUMBER | CHAR | CHAR (n [CHAR|BYTE]) |\n" |
| 589 | + "\t\t\t VARCHAR2 (n [CHAR|BYTE]) | NCHAR | NCHAR (n) |\n" |
| 590 | + "\t\t\t NVARCHAR2 (n) | BINARY_FLOAT | BINARY_DOUBLE ] ]"); |
| 591 | + } |
| 592 | + else if (token && pg_strcasecmp(token, "print") == 0) |
| 593 | + { |
| 594 | + print_list *pl = pg_malloc0(sizeof(print_list)); |
| 595 | + |
| 596 | + pl->items = NULL; |
| 597 | + pl->length = 0; |
| 598 | + |
| 599 | + token = strtokx(NULL, whitespace, NULL, NULL, |
| 600 | + 0, false, false, pset.encoding); |
| 601 | + while (token) |
| 602 | + { |
| 603 | + print_item *item; |
| 604 | + |
| 605 | + pl->length++; |
| 606 | + |
| 607 | + if (pl->length == 1) |
| 608 | + pl->items = (print_item **) pg_malloc0(sizeof(print_item *) * 1); |
| 609 | + else |
| 610 | + pl->items = (print_item **) pg_realloc(pl->items, sizeof(print_item *) * pl->length); |
| 611 | + |
| 612 | + /* Strips the leading and trailing quote characters of double quotes */ |
| 613 | + if (token[0] == '"' && token[strlen(token) - 1] == '"') |
| 614 | + { |
| 615 | + token[strlen(token) - 1] = '\0'; |
| 616 | + token++; |
| 617 | + } |
| 618 | + |
| 619 | + item = pg_malloc0(sizeof(print_item)); |
| 620 | + item->bv_name = pg_strdup(token); |
| 621 | + |
| 622 | + if (ValidBindVariableName(item->bv_name)) |
| 623 | + item->valid = true; |
| 624 | + else |
| 625 | + item->valid = false; |
| 626 | + pl->items[pl->length - 1] = item; |
| 627 | + token = strtokx(NULL, whitespace, NULL, NULL, |
| 628 | + 0, false, false, pset.encoding); |
| 629 | + } |
| 630 | + |
| 631 | + if (pl->length == 0) |
| 632 | + PrintBindVariables(pset.vars, NULL); |
| 633 | + else |
| 634 | + PrintBindVariables(pset.vars, pl); |
| 635 | + |
| 636 | + while (pl->length > 0) |
| 637 | + { |
| 638 | + pl->length--; |
| 639 | + if (pl->items && |
| 640 | + pl->items[pl->length] && |
| 641 | + pl->items[pl->length]->bv_name) |
| 642 | + { |
| 643 | + pg_free(pl->items[pl->length]->bv_name); |
| 644 | + pg_free(pl->items[pl->length]); |
| 645 | + } |
| 646 | + |
| 647 | + if (pl->length == 0) |
| 648 | + { |
| 649 | + pg_free(pl->items); |
| 650 | + pg_free(pl); |
| 651 | + } |
| 652 | + } |
| 653 | + } |
| 654 | + |
| 655 | + /* save client command in history */ |
| 656 | + if (pset.cur_cmd_interactive) |
| 657 | + { |
| 658 | + pg_append_history(psqlplus_line, history_buf); |
| 659 | + pg_send_history(history_buf); |
| 660 | + } |
| 661 | + |
| 662 | + /* reset */ |
| 663 | + pset.stmt_lineno = 1; |
| 664 | + resetPQExpBuffer(query_buf); |
| 665 | + psql_scan_finish(scan_state); |
| 666 | + ora_psql_scan_finish(ora_scan_state); |
| 667 | + free(psqlplus_line); |
| 668 | + free(line); |
| 669 | + psqlplus_scanner_finish(yyscanner); |
| 670 | + ora_psql_scan_destroy(pstate); |
| 671 | + continue; |
| 672 | + } |
| 673 | + |
| 674 | + /* Not a compatible-oracle client command */ |
| 675 | + psqlplus_scanner_finish(yyscanner); |
| 676 | + ora_psql_scan_destroy(pstate); |
| 677 | + free(psqlplus_line); |
| 678 | + } |
431 | 679 |
|
432 | 680 | success = true; |
433 | 681 | line_saved_in_history = false; |
|
0 commit comments