001package es.ucm.fdi.gaia.jcolibri.test.database; 002 003import java.io.BufferedReader; 004import java.io.ByteArrayOutputStream; 005import java.io.File; 006import java.io.FileInputStream; 007import java.io.FileOutputStream; 008import java.io.IOException; 009import java.io.InputStream; 010import java.io.InputStreamReader; 011import java.io.OutputStreamWriter; 012import java.io.PrintStream; 013import java.io.PrintWriter; 014import java.io.StringWriter; 015import java.sql.Connection; 016import java.sql.DatabaseMetaData; 017import java.sql.PreparedStatement; 018import java.sql.ResultSet; 019import java.sql.ResultSetMetaData; 020import java.sql.SQLException; 021import java.sql.Statement; 022import java.util.ArrayList; 023import java.util.HashMap; 024import java.util.Iterator; 025import java.util.Map; 026import java.util.StringTokenizer; 027import java.util.TreeMap; 028 029import org.hsqldb.cmdline.*; 030 031@SuppressWarnings("all") 032public class SqlFile 033{ 034 private static final int DEFAULT_HISTORY_SIZE = 20; 035 private File file; 036 private boolean interactive; 037 private String primaryPrompt = "sql> "; 038 private String chunkPrompt = "raw> "; 039 private String contPrompt = " +> "; 040 private Connection curConn = null; 041 private boolean htmlMode = false; 042 private HashMap userVars = null; 043 private String[] statementHistory = null; 044 private boolean chunking = false; 045 private String csvNullRep = null; 046 047 /** 048 * Private class to "share" a variable among a family of SqlFile 049 * instances. 050 */ 051 private static class BooleanBucket { 052 053 private boolean bPriv = false; 054 055 public void set(boolean bIn) { 056 bPriv = bIn; 057 } 058 059 public boolean get() { 060 return bPriv; 061 } 062 } 063 064 // This is an imperfect solution since when user runs SQL they could 065 // be running DDL or a commit or rollback statement. All we know is, 066 // they MAY run some DML that needs to be committed. 067 BooleanBucket possiblyUncommitteds = new BooleanBucket(); 068 069 // Ascii field separator blanks 070 private static final int SEP_LEN = 2; 071 private static final String DIVIDER = 072 "-----------------------------------------------------------------" 073 + "-----------------------------------------------------------------"; 074 private static final String SPACES = 075 " " 076 + " "; 077 private static String revnum = null; 078 079 static { 080 revnum = "$Revision: 1.135 $".substring("$Revision: ".length(), 081 "$Revision: 1.135 $".length() - 2); 082 } 083 084 private static String BANNER = 085 "(SqlFile processor v. " + revnum + ")\n" 086 + "Distribution is permitted under the terms of the HSQLDB license.\n" 087 + "(c) 2004-2005 Blaine Simpson and the HSQLDB Development Group.\n\n" 088 + " \\q to Quit.\n" + " \\? lists Special Commands.\n" 089 + " :? lists Buffer/Editing commands.\n" 090 + " *? lists PL commands (including alias commands).\n\n" 091 + "SPECIAL Commands begin with '\\' and execute when you hit ENTER.\n" 092 + "BUFFER Commands begin with ':' and execute when you hit ENTER.\n" 093 + "COMMENTS begin with '/*' and end with the very next '*/'.\n" 094 + "PROCEDURAL LANGUAGE commands begin with '*' and end when you hit ENTER.\n" 095 + "All other lines comprise SQL Statements.\n" 096 + " SQL Statements are terminated by either a blank line (which moves the\n" 097 + " statement into the buffer without executing) or a line ending with ';'\n" 098 + " (which executes the statement).\n" 099 + " SQL Statements may begin with '/PLVARNAME' and/or contain *{PLVARNAME}s.\n"; 100 private static final String BUFFER_HELP_TEXT = 101 "BUFFER Commands (only \":;\" is available for non-interactive use).\n" 102 + " :? Help\n" 103 + " :; Execute current buffer as an SQL Statement\n" 104 + " :a[text] Enter append mode with a copy of the buffer\n" 105 + " :l List current contents of buffer\n" 106 + " :s/from/to Substitute \"to\" for first occurrence of \"from\"\n" 107 + " :s/from/to/[i;g2] Substitute \"to\" for occurrence(s) of \"from\"\n" 108 + " from: '$'s represent line breaks\n" 109 + " to: If empty, from's will be deleted (e.g. \":s/x//\").\n" 110 + " '$'s represent line breaks\n" 111 + " You can't use ';' in order to execute the SQL (use\n" 112 + " the ';' switch for this purpose, as explained below).\n" 113 + " /: Can actually be any character which occurs in\n" 114 + " neither \"to\" string nor \"from\" string.\n" 115 + " SUBSTITUTION MODE SWITCHES:\n" 116 + " i: case Insensitive\n" 117 + " ;: execute immediately after substitution\n" 118 + " g: Global (substitute ALL occurrences of \"from\" string)\n" 119 + " 2: Narrows substitution to specified buffer line number\n" 120 + " (Use any line number in place of '2').\n" 121 ; 122 private static final String HELP_TEXT = "SPECIAL Commands.\n" 123 + "* commands only available for interactive use.\n" 124 + "In place of \"3\" below, you can use nothing for the previous command, or\n" 125 + "an integer \"X\" to indicate the Xth previous command.\n" 126 + "Filter substrings are cases-sensitive! Use \"SCHEMANAME.\" to narrow schema.\n" 127 + " \\? Help\n" 128 + " \\p [line to print] Print string to stdout\n" 129 + " \\w file/path.sql Append current buffer to file\n" 130 + " \\i file/path.sql Include/execute commands from external file\n" 131 + " \\d{tvsiSanur*} [substr] List objects of specified type:\n" 132 + " (Tbls/Views/Seqs/Indexes/SysTbls/Aliases/schemaNames/Users/Roles/table-like)\n" 133 + " \\d OBJECTNAME [subs] Describe table or view columns\n" 134 + " \\o [file/path.html] Tee (or stop teeing) query output to specified file\n" 135 + " \\H Toggle HTML output mode\n" 136 + " \\! COMMAND ARGS Execute external program (no support for stdin)\n" 137 + " \\c [true|false] Continue upon errors (a.o.t. abort upon error)\n" 138 + " \\a [true|false] Auto-commit JDBC DML commands\n" 139 + " \\b save next result to Binary buffer (no display)\n" 140 + " \\bd file/path.bin Dump Binary buffer to file\n" 141 + " \\bl file/path.bin Load file into Binary buffer\n" 142 + " \\bp Use ? in next SQL statement to upload Bin. buffer\n" 143 + " \\. Enter raw SQL. End with line containing only \".\"\n" 144 + " \\s * Show previous commands (i.e. SQL command history)\n" 145 + " \\-[3][;] * reload a command to buffer (opt. exec. w/ \":;\"))\n" 146 + " \\x {TABLE|SELECT...} eXport table or query to CSV text file\n" 147 + " \\m file/path.csv iMport CSV text file records into a table\n" 148 + " \\q [abort message] Quit (or end input like Ctrl-Z or Ctrl-D)\n" 149 ; 150 private static final String PL_HELP_TEXT = "PROCEDURAL LANGUAGE Commands.\n" 151 + " *? Help\n" 152 + " * Expand PL variables from now on.\n" 153 + " (this is also implied by all the following).\n" 154 + " * VARNAME = Variable value Set variable value\n" 155 + " * VARNAME = Unset variable\n" 156 + " * VARNAME ~ Set variable value to the value of the very\n" 157 + " next SQL statement executed (see details\n" 158 + " at the bottom of this listing).\n" 159 + " * VARNAME _ Same as * VARNAME _, except the query is\n" 160 + " done silently (i.e, no rows to screen)\n" 161 + " * list[value] [VARNAME1...] List variable(s) (defaults to all)\n" 162 + " * load VARNAME path.txt Load variable value from text file\n" 163 + " * dump VARNAME path.txt Dump variable value to text file\n" 164 + " * prepare VARNAME Use ? in next SQL statement to upload val.\n" 165 + " * foreach VARNAME ([val1...]) Repeat the following PL block with the\n" 166 + " variable set to each value in turn.\n" 167 + " * if (logical expr) Execute following PL block only if expr true\n" 168 + " * while (logical expr) Repeat following PL block while expr true\n" 169 + " * end foreach|if|while Ends a PL block\n" 170 + " * break [foreach|if|while|file] Exits a PL block or file early\n" 171 + " * continue [foreach|while] Exits a PL block iteration early\n\n" 172 + "Use PL variables (which you have set) like: *{VARNAME}.\n" 173 + "You may use /VARNAME instead iff /VARNAME is the first word of a SQL command.\n" 174 + "Use PL variables in logical expressions like: *VARNAME.\n\n" 175 + "'* VARNAME ~' or '* VARNAME _' sets the variable value according to the very\n" 176 + "next SQL statement (~ will echo the value, _ will do it silently):\n" 177 + " Query: The value of the first field of the first row returned.\n" 178 + " other: Return status of the command (for updates this will be\n" 179 + " the number of rows updated).\n" 180 ; 181 182 /** 183 * Interpret lines of input file as SQL Statements, Comments, 184 * Special Commands, and Buffer Commands. 185 * Most Special Commands and many Buffer commands are only for 186 * interactive use. 187 * 188 * @param inFile inFile of null means to read stdin. 189 * @param inInteractive If true, prompts are printed, the interactive 190 * Special commands are enabled, and 191 * continueOnError defaults to true. 192 */ 193 public SqlFile(File inFile, boolean inInteractive, 194 HashMap<?,?> inVars) throws IOException { 195 196 file = inFile; 197 interactive = inInteractive; 198 userVars = inVars; 199 200 try { 201 statementHistory = 202 new String[interactive ? Integer.parseInt(System.getProperty("sqltool.historyLength")) 203 : 1]; 204 } catch (Throwable t) { 205 statementHistory = null; 206 } 207 208 if (statementHistory == null) { 209 statementHistory = new String[DEFAULT_HISTORY_SIZE]; 210 } 211 212 if (file == null) { 213 throw new IOException("Can't read SQL file '" + file + "'"); 214 } 215 } 216 217 /** 218 * Constructor for reading stdin instead of a file for commands. 219 * 220 */ 221 public SqlFile(boolean inInteractive, HashMap inVars) throws IOException { 222 this(null, inInteractive, inVars); 223 } 224 225 /** 226 * Process all the commands on stdin. 227 * 228 * @param conn The JDBC connection to use for SQL Commands. 229 */ 230 public void execute(Connection conn, 231 Boolean coeOverride) 232 throws IOException, SqlToolError, SQLException { 233 execute(conn, System.out, System.err, coeOverride); 234 } 235 236 /** 237 * Process all the commands on stdin. 238 * 239 * @param conn The JDBC connection to use for SQL Commands. 240 * @throws SqlToolError 241 */ 242 public void execute(Connection conn, 243 boolean coeOverride) 244 throws IOException, SQLException, SqlToolError { 245 execute(conn, System.out, System.err, new Boolean(coeOverride)); 246 } 247 248 // So we can tell how to handle quit and break commands. 249 public boolean recursed = false; 250 private String curCommand = null; 251 private int curLinenum = -1; 252 private int curHist = -1; 253 private PrintStream psStd = null; 254 private PrintStream psErr = null; 255 private PrintWriter pwQuery = null; 256 private PrintWriter pwCsv = null; 257 StringBuffer stringBuffer = new StringBuffer(); 258 /* 259 * This is reset upon each execute() invocation (to true if interactive, 260 * false otherwise). 261 */ 262 private boolean continueOnError = false; 263 private static final String DEFAULT_CHARSET = "US-ASCII"; 264 private BufferedReader br = null; 265 private String charset = null; 266 267 /** 268 * Process all the commands in the file (or stdin) associated with 269 * "this" object. 270 * Run SQL in the file through the given database connection. 271 * 272 * This is synchronized so that I can use object variables to keep 273 * track of current line number, command, connection, i/o streams, etc. 274 * 275 * Sets encoding character set to that specified with System Property 276 * 'sqlfile.charset'. Defaults to "US-ASCII". 277 * 278 * @param conn The JDBC connection to use for SQL Commands. 279 */ 280 public synchronized void execute(Connection conn, PrintStream stdIn, 281 PrintStream errIn, 282 Boolean coeOverride) 283 throws IOException, SqlToolError, 284 SQLException { 285 286 psStd = stdIn; 287 psErr = errIn; 288 curConn = conn; 289 curLinenum = -1; 290 291 String inputLine; 292 String trimmedCommand; 293 String trimmedInput; 294 String deTerminated; 295 boolean inComment = false; // Globbling up a comment 296 int postCommentIndex; 297 boolean gracefulExit = false; 298 299 continueOnError = (coeOverride == null) ? interactive 300 : coeOverride.booleanValue(); 301 302 if (userVars != null && userVars.size() > 0) { 303 plMode = true; 304 } 305 306 String specifiedCharSet = System.getProperty("sqlfile.charset"); 307 308 charset = ((specifiedCharSet == null) ? DEFAULT_CHARSET 309 : specifiedCharSet); 310 311 try { 312 //br = new BufferedReader(new InputStreamReader((file == null) 313 // ? System.in 314 // : new FileInputStream(file), charset)); 315 br = new BufferedReader( new InputStreamReader(es.ucm.fdi.gaia.jcolibri.util.FileIO.openFile(file.toString()))); 316 curLinenum = 0; 317 318 if (interactive) { 319 stdprintln(BANNER); 320 } 321 322 while (true) { 323 if (interactive) { 324 psStd.print((stringBuffer.length() == 0) 325 ? (chunking ? chunkPrompt 326 : primaryPrompt) 327 : contPrompt); 328 } 329 330 inputLine = br.readLine(); 331 332 if (inputLine == null) { 333 /* 334 * This is because interactive EOD on some OSes doesn't 335 * send a line-break, resulting in no linebreak at all 336 * after the SqlFile prompt or whatever happens to be 337 * on their screen. 338 */ 339 if (interactive) { 340 psStd.println(); 341 } 342 343 break; 344 } 345 346 curLinenum++; 347 348 if (chunking) { 349 if (inputLine.equals(".")) { 350 chunking = false; 351 352 setBuf(stringBuffer.toString()); 353 stringBuffer.setLength(0); 354 355 if (interactive) { 356 stdprintln("Raw SQL chunk moved into buffer. " 357 + "Run \":;\" to execute the chunk."); 358 } 359 } else { 360 if (stringBuffer.length() > 0) { 361 stringBuffer.append('\n'); 362 } 363 364 stringBuffer.append(inputLine); 365 } 366 367 continue; 368 } 369 370 if (inComment) { 371 postCommentIndex = inputLine.indexOf("*/") + 2; 372 373 if (postCommentIndex > 1) { 374 375 // I see no reason to leave comments in history. 376 inputLine = inputLine.substring(postCommentIndex); 377 378 // Empty the buffer. The non-comment remainder of 379 // this line is either the beginning of a new SQL 380 // or Special command, or an empty line. 381 stringBuffer.setLength(0); 382 383 inComment = false; 384 } else { 385 386 // Just completely ignore the input line. 387 continue; 388 } 389 } 390 391 trimmedInput = inputLine.trim(); 392 393 try { 394 395 // This is the try for SQLException. SQLExceptions are 396 // normally thrown below in Statement processing, but 397 // could be called up above if a Special processing 398 // executes a SQL command from history. 399 if (stringBuffer.length() == 0) { 400 if (trimmedInput.startsWith("/*")) { 401 postCommentIndex = trimmedInput.indexOf("*/", 2) 402 + 2; 403 404 if (postCommentIndex > 1) { 405 406 // I see no reason to leave comments in 407 // history. 408 inputLine = inputLine.substring( 409 postCommentIndex + inputLine.length() 410 - trimmedInput.length()); 411 trimmedInput = inputLine.trim(); 412 } else { 413 414 // Just so we get continuation lines: 415 stringBuffer.append("COMMENT"); 416 417 inComment = true; 418 419 continue; 420 } 421 } 422 423 // This is just to filter out useless newlines at 424 // beginning of commands. 425 if (trimmedInput.length() == 0) { 426 continue; 427 } 428 429 if (trimmedInput.charAt(0) == '*' 430 && (trimmedInput.length() < 2 431 || trimmedInput.charAt(1) != '{')) { 432 try { 433 processPL((trimmedInput.length() == 1) ? "" 434 : trimmedInput 435 .substring(1) 436 .trim()); 437 } catch (BadSpecial bs) { 438 errprintln("Error at '" 439 + ((file == null) ? "stdin" 440 : file.toString()) + "' line " 441 + curLinenum 442 + ":\n\"" 443 + inputLine 444 + "\"\n" 445 + bs.getMessage()); 446 447 if (!continueOnError) { 448 throw new SqlToolError(bs); 449 } 450 } 451 452 continue; 453 } 454 455 if (trimmedInput.charAt(0) == '\\') { 456 try { 457 processSpecial(trimmedInput.substring(1)); 458 } catch (BadSpecial bs) { 459 errprintln("Error at '" 460 + ((file == null) ? "stdin" 461 : file.toString()) + "' line " 462 + curLinenum 463 + ":\n\"" 464 + inputLine 465 + "\"\n" 466 + bs.getMessage()); 467 468 if (!continueOnError) { 469 throw new SqlToolError(bs); 470 } 471 } 472 473 continue; 474 } 475 476 if (trimmedInput.charAt(0) == ':' 477 && (interactive 478 || (trimmedInput.charAt(1) == ';'))) { 479 try { 480 processBuffer(trimmedInput.substring(1)); 481 } catch (BadSpecial bs) { 482 errprintln("Error at '" 483 + ((file == null) ? "stdin" 484 : file.toString()) + "' line " 485 + curLinenum 486 + ":\n\"" 487 + inputLine 488 + "\"\n" 489 + bs.getMessage()); 490 491 if (!continueOnError) { 492 throw new SqlToolError(bs); 493 } 494 } 495 496 continue; 497 } 498 499 String ucased = trimmedInput.toUpperCase(); 500 501 if (ucased.startsWith("DECLARE") 502 || ucased.startsWith("BEGIN")) { 503 chunking = true; 504 505 stringBuffer.append(inputLine); 506 507 if (interactive) { 508 stdprintln( 509 "Enter RAW SQL. No \\, :, * commands. " 510 + "End with a line containing only \".\":"); 511 } 512 513 continue; 514 } 515 } 516 517 if (trimmedInput.length() == 0) { 518 519 // Blank lines delimit commands ONLY IN INTERACTIVE 520 // MODE! 521 if (interactive &&!inComment) { 522 setBuf(stringBuffer.toString()); 523 stringBuffer.setLength(0); 524 stdprintln("Current input moved into buffer."); 525 } 526 527 continue; 528 } 529 530 deTerminated = deTerminated(inputLine); 531 532 // A null terminal line (i.e., /\s*;\s*$/) is never useful. 533 if (!trimmedInput.equals(";")) { 534 if (stringBuffer.length() > 0) { 535 stringBuffer.append('\n'); 536 } 537 538 stringBuffer.append((deTerminated == null) ? inputLine 539 : deTerminated); 540 } 541 542 if (deTerminated == null) { 543 continue; 544 } 545 546 // If we reach here, then stringBuffer contains a complete 547 // SQL command. 548 curCommand = stringBuffer.toString(); 549 trimmedCommand = curCommand.trim(); 550 551 if (trimmedCommand.length() == 0) { 552 throw new SQLException("Empty SQL Statement"); 553 } 554 555 setBuf(curCommand); 556 processSQL(); 557 } catch (SQLException se) { 558 errprintln("SQL Error at '" + ((file == null) ? "stdin" 559 : file.toString()) + "' line " 560 + curLinenum 561 + ":\n\"" 562 + curCommand 563 + "\"\n" 564 + se 565 .getMessage()); 566 567 if (!continueOnError) { 568 throw se; 569 } 570 } catch (BreakException be) { 571 String msg = be.getMessage(); 572 573 if ((!recursed) && (msg != null &&!msg.equals("file"))) { 574 errprintln("Unsatisfied break statement" 575 + ((msg == null) ? "" 576 : (" (type " + msg 577 + ')')) + '.'); 578 } else { 579 gracefulExit = true; 580 } 581 582 if (recursed ||!continueOnError) { 583 throw be; 584 } 585 } catch (ContinueException ce) { 586 String msg = ce.getMessage(); 587 588 if (!recursed) { 589 errprintln("Unsatisfied continue statement" 590 + ((msg == null) ? "" 591 : (" (type " + msg 592 + ')')) + '.'); 593 } else { 594 gracefulExit = true; 595 } 596 597 if (recursed ||!continueOnError) { 598 throw ce; 599 } 600 } catch (QuitNow qn) { 601 throw qn; 602 } catch (SqlToolError ste) { 603 if (!continueOnError) { 604 throw ste; 605 } 606 } 607 608 stringBuffer.setLength(0); 609 } 610 611 if (inComment || stringBuffer.length() != 0) { 612 errprintln("Unterminated input: [" + stringBuffer + ']'); 613 614 throw new SqlToolError("Unterminated input: [" 615 + stringBuffer + ']'); 616 } 617 618 gracefulExit = true; 619 } catch (QuitNow qn) { 620 gracefulExit = qn.getMessage() == null; 621 622 if ((!recursed) &&!gracefulExit) { 623 errprintln("Aborting: " + qn.getMessage()); 624 } 625 626 if (recursed ||!gracefulExit) { 627 throw qn; 628 } 629 630 return; 631 } finally { 632 closeQueryOutputStream(); 633 634 if (fetchingVar != null) { 635 errprintln("PL variable setting incomplete: " + fetchingVar); 636 637 gracefulExit = false; 638 } 639 640 if (br != null) { 641 br.close(); 642 } 643 644 if ((!gracefulExit) && possiblyUncommitteds.get()) { 645 errprintln("Rolling back SQL transaction."); 646 curConn.rollback(); 647 possiblyUncommitteds.set(false); 648 } 649 } 650 } 651 652 /** 653 * Returns a copy of given string without a terminating semicolon. 654 * If there is no terminating semicolon, null is returned. 655 * 656 * @param inString Base String, which will not be modified (because 657 * a "copy" will be returned). 658 */ 659 private static String deTerminated(String inString) { 660 661 int index = inString.lastIndexOf(';'); 662 663 if (index < 0) { 664 return null; 665 } 666 667 for (int i = index + 1; i < inString.length(); i++) { 668 if (!Character.isWhitespace(inString.charAt(i))) { 669 return null; 670 } 671 } 672 673 return inString.substring(0, index); 674 } 675 676 /** 677 * Utility nested Exception class for internal use. 678 */ 679 private class BadSpecial extends Exception { 680 681 /** 682 * 683 */ 684 private static final long serialVersionUID = 1L; 685 686 // Special-purpose constructor 687 private BadSpecial() {} 688 689 // Normal use constructor 690 private BadSpecial(String s) { 691 super(s); 692 } 693 } 694 695 /** 696 * Utility nested Exception class for internal use. 697 * This must extend SqlToolError because it has to percolate up from 698 * recursions of SqlTool.execute(), yet SqlTool.execute() is public 699 * and external users should not declare (or expect!) QuitNows to be 700 * thrown. 701 * SqlTool.execute() on throws a QuitNow if it is in a recursive call. 702 */ 703 private class QuitNow extends SqlToolError { 704 705 /** 706 * 707 */ 708 private static final long serialVersionUID = 1L; 709 710 public QuitNow(String s) { 711 super(s); 712 } 713 714 public QuitNow() { 715 super(); 716 } 717 } 718 719 /** 720 * Utility nested Exception class for internal use. 721 * Very similar to QuitNow. 722 */ 723 private class BreakException extends SqlToolError { 724 725 /** 726 * 727 */ 728 private static final long serialVersionUID = 1L; 729 730 public BreakException() { 731 super(); 732 } 733 734 public BreakException(String s) { 735 super(s); 736 } 737 } 738 739 /** 740 * Utility nested Exception class for internal use. 741 * Very similar to QuitNow. 742 */ 743 private class ContinueException extends SqlToolError { 744 745 /** 746 * 747 */ 748 private static final long serialVersionUID = 1L; 749 750 public ContinueException() { 751 super(); 752 } 753 754 public ContinueException(String s) { 755 super(s); 756 } 757 } 758 759 /** 760 * Utility nested Exception class for internal use. 761 */ 762 private class BadSwitch extends Exception { 763 764 /** 765 * 766 */ 767 private static final long serialVersionUID = 1L; 768 769 private BadSwitch(int i) { 770 super(Integer.toString(i)); 771 } 772 } 773 774 /** 775 * Process a Buffer/Edit Command. 776 * 777 * Due to the nature of the goal here, we don't trim() "other" like 778 * we do for other kinds of commands. 779 * 780 * @param inString Complete command, less the leading ':' character. 781 * @throws SQLException Passed through from processSQL() 782 * @throws BadSpecial Runtime error() 783 */ 784 @SuppressWarnings("unused") 785 private void processBuffer(String inString) 786 throws BadSpecial, SQLException { 787 788 789 int index = 0; 790 int special; 791 char commandChar = 'i'; 792 String other = null; 793 794 if (inString.length() > 0) { 795 commandChar = inString.charAt(0); 796 other = inString.substring(1); 797 798 if (other.trim().length() == 0) { 799 other = null; 800 } 801 } 802 803 switch (commandChar) { 804 805 case ';' : 806 curCommand = commandFromHistory(0); 807 808 stdprintln("Executing command from buffer:\n" + curCommand 809 + '\n'); 810 processSQL(); 811 812 return; 813 814 case 'a' : 815 case 'A' : 816 stringBuffer.append(commandFromHistory(0)); 817 818 if (other != null) { 819 String deTerminated = deTerminated(other); 820 821 if (!other.equals(";")) { 822 stringBuffer.append(((deTerminated == null) ? other 823 : deTerminated)); 824 } 825 826 if (deTerminated != null) { 827 828 // If we reach here, then stringBuffer contains a 829 // complete SQL command. 830 curCommand = stringBuffer.toString(); 831 832 setBuf(curCommand); 833 stdprintln("Executing:\n" + curCommand + '\n'); 834 processSQL(); 835 stringBuffer.setLength(0); 836 837 return; 838 } 839 } 840 841 stdprintln("Appending to:\n" + stringBuffer); 842 843 return; 844 845 case 'l' : 846 case 'L' : 847 stdprintln("Current Buffer:\n" + commandFromHistory(0)); 848 849 return; 850 851 case 's' : 852 case 'S' : 853 854 // Sat Apr 23 14:14:57 EDT 2005. Changing history behavior. 855 // It's very inconvenient to lose all modified SQL 856 // commands from history just because _some_ may be modified 857 // because they are bad or obsolete. 858 boolean modeIC = false; 859 boolean modeGlobal = false; 860 boolean modeExecute = false; 861 int modeLine = 0; 862 863 try { 864 String fromHist = commandFromHistory(0); 865 StringBuffer sb = new StringBuffer(fromHist); 866 867 if (other == null) { 868 throw new BadSwitch(0); 869 } 870 871 String delim = other.substring(0, 1); 872 StringTokenizer toker = new StringTokenizer(other, delim, 873 true); 874 875 if (toker.countTokens() < 4 876 ||!toker.nextToken().equals(delim)) { 877 throw new BadSwitch(1); 878 } 879 880 String from = toker.nextToken().replace('$', '\n'); 881 882 if (!toker.nextToken().equals(delim)) { 883 throw new BadSwitch(2); 884 } 885 886 String to = toker.nextToken().replace('$', '\n'); 887 888 if (to.equals(delim)) { 889 to = ""; 890 } else { 891 if (toker.countTokens() > 0 892 &&!toker.nextToken().equals(delim)) { 893 throw new BadSwitch(3); 894 } 895 } 896 897 if (toker.countTokens() > 0) { 898 String opts = toker.nextToken(""); 899 900 for (int j = 0; j < opts.length(); j++) { 901 switch (opts.charAt(j)) { 902 903 case 'i' : 904 modeIC = true; 905 break; 906 907 case ';' : 908 modeExecute = true; 909 break; 910 911 case 'g' : 912 modeGlobal = true; 913 break; 914 915 case '1' : 916 case '2' : 917 case '3' : 918 case '4' : 919 case '5' : 920 case '6' : 921 case '7' : 922 case '8' : 923 case '9' : 924 modeLine = Character.digit(opts.charAt(j), 925 10); 926 break; 927 928 default : 929 throw new BadSpecial( 930 "Unknown Substitution option: " 931 + opts.charAt(j)); 932 } 933 } 934 } 935 936 if (modeIC) { 937 fromHist = fromHist.toUpperCase(); 938 from = from.toUpperCase(); 939 } 940 941 // lineStart will be either 0 or char FOLLOWING a \n. 942 int lineStart = 0; 943 944 // lineStop is the \n AFTER what we consider. 945 int lineStop = -1; 946 947 if (modeLine > 0) { 948 for (int j = 1; j < modeLine; j++) { 949 lineStart = fromHist.indexOf('\n', lineStart) + 1; 950 951 if (lineStart < 1) { 952 throw new BadSpecial( 953 "There are not " + modeLine 954 + " lines in the buffer."); 955 } 956 } 957 958 lineStop = fromHist.indexOf('\n', lineStart); 959 } 960 961 if (lineStop < 0) { 962 lineStop = fromHist.length(); 963 } 964 965 // System.err.println("[" 966 // + fromHist.substring(lineStart, lineStop) + ']'); 967 int i; 968 969 if (modeGlobal) { 970 i = lineStop; 971 972 while ((i = fromHist.lastIndexOf(from, i - 1)) 973 >= lineStart) { 974 sb.replace(i, i + from.length(), to); 975 } 976 } else if ((i = fromHist.indexOf(from, lineStart)) > -1 977 && i < lineStop) { 978 sb.replace(i, i + from.length(), to); 979 } 980 981 //statementHistory[curHist] = sb.toString(); 982 curCommand = sb.toString(); 983 984 setBuf(curCommand); 985 stdprintln((modeExecute ? "Executing" 986 : "Current Buffer") + ":\n" 987 + curCommand); 988 989 if (modeExecute) { 990 stdprintln(); 991 } 992 } catch (BadSwitch badswitch) { 993 throw new BadSpecial( 994 "Substitution syntax: \":s/from this/to that/i;g2\". " 995 + "Use '$' for line separations. [" 996 + badswitch.getMessage() + ']'); 997 } 998 999 if (modeExecute) { 1000 processSQL(); 1001 stringBuffer.setLength(0); 1002 } 1003 1004 return; 1005 1006 case '?' : 1007 stdprintln(BUFFER_HELP_TEXT); 1008 1009 return; 1010 } 1011 1012 throw new BadSpecial("Unknown Buffer Command"); 1013 } 1014 1015 private boolean doPrepare = false; 1016 private String prepareVar = null; 1017 private String csvColDelim = null; 1018 private String csvRowDelim = null; 1019 private static final String CSV_SYNTAX_MSG = 1020 "Export syntax: x table_or_view_anme " 1021 + "[column_delimiter [record_delimiter]]"; 1022 1023 /** 1024 * Process a Special Command. 1025 * 1026 * @param inString Complete command, less the leading '\' character. 1027 * @throws SQLException Passed through from processSQL() 1028 * @throws BadSpecial Runtime error() 1029 * @throws QuitNot Command execution (but not the JVM!) should stop 1030 */ 1031 @SuppressWarnings("unused") 1032 private void processSpecial(String inString) 1033 throws BadSpecial, QuitNow, SQLException, SqlToolError { 1034 1035 int index = 0; 1036 int special; 1037 String arg1, 1038 other = null; 1039 1040 if (inString.length() < 1) { 1041 throw new BadSpecial("Null special command"); 1042 } 1043 1044 if (plMode) { 1045 inString = dereference(inString, false); 1046 } 1047 1048 StringTokenizer toker = new StringTokenizer(inString); 1049 1050 arg1 = toker.nextToken(); 1051 1052 if (toker.hasMoreTokens()) { 1053 other = toker.nextToken("").trim(); 1054 } 1055 1056 switch (arg1.charAt(0)) { 1057 1058 case 'q' : 1059 if (other != null) { 1060 throw new QuitNow(other); 1061 } 1062 1063 throw new QuitNow(); 1064 case 'H' : 1065 htmlMode = !htmlMode; 1066 1067 stdprintln("HTML Mode is now set to: " + htmlMode); 1068 1069 return; 1070 1071 case 'm' : 1072 if (arg1.length() != 1 || other == null) { 1073 throw new BadSpecial(); 1074 } 1075 1076 csvColDelim = 1077 convertEscapes((String) userVars.get("*CSV_COL_DELIM")); 1078 csvRowDelim = 1079 convertEscapes((String) userVars.get("*CSV_ROW_DELIM")); 1080 csvNullRep = (String) userVars.get("*CSV_NULL_REP"); 1081 1082 if (csvColDelim == null) { 1083 csvColDelim = DEFAULT_COL_DELIM; 1084 } 1085 1086 if (csvRowDelim == null) { 1087 csvRowDelim = DEFAULT_ROW_DELIM; 1088 } 1089 1090 if (csvNullRep == null) { 1091 csvNullRep = DEFAULT_NULL_REP; 1092 } 1093 1094 try { 1095 importCsv(other); 1096 } catch (IOException ioe) { 1097 System.err.println("Failed to read in CSV file: " + ioe); 1098 } 1099 1100 return; 1101 1102 case 'x' : 1103 try { 1104 if (arg1.length() != 1 || other == null) { 1105 throw new BadSpecial(); 1106 } 1107 1108 String tableName = ((other.indexOf(' ') > 0) ? null 1109 : other); 1110 1111 csvColDelim = convertEscapes( 1112 (String) userVars.get("*CSV_COL_DELIM")); 1113 csvRowDelim = convertEscapes( 1114 (String) userVars.get("*CSV_ROW_DELIM")); 1115 csvNullRep = (String) userVars.get("*CSV_NULL_REP"); 1116 1117 String csvFilepath = 1118 (String) userVars.get("*CSV_FILEPATH"); 1119 1120 if (csvFilepath == null && tableName == null) { 1121 throw new BadSpecial( 1122 "You must set PL variable '*CSV_FILEPATH' in " 1123 + "order to use the query variant of \\x"); 1124 } 1125 1126 File csvFile = new File((csvFilepath == null) 1127 ? (tableName + ".csv") 1128 : csvFilepath); 1129 1130 if (csvColDelim == null) { 1131 csvColDelim = DEFAULT_COL_DELIM; 1132 } 1133 1134 if (csvRowDelim == null) { 1135 csvRowDelim = DEFAULT_ROW_DELIM; 1136 } 1137 1138 if (csvNullRep == null) { 1139 csvNullRep = DEFAULT_NULL_REP; 1140 } 1141 1142 pwCsv = new PrintWriter( 1143 new OutputStreamWriter( 1144 new FileOutputStream(csvFile), charset)); 1145 1146 displayResultSet( 1147 null, 1148 curConn.createStatement().executeQuery( 1149 (tableName == null) ? other 1150 : ("SELECT * FROM " 1151 + tableName)), null, null); 1152 pwCsv.flush(); 1153 stdprintln("Wrote " + csvFile.length() 1154 + " characters to file '" + csvFile + "'"); 1155 } catch (Exception e) { 1156 if (e instanceof BadSpecial) { 1157 1158 // Not sure this test is right. Maybe .length() == 0? 1159 if (e.getMessage() == null) { 1160 throw new BadSpecial(CSV_SYNTAX_MSG); 1161 } else { 1162 throw (BadSpecial) e; 1163 } 1164 } 1165 1166 throw new BadSpecial("Failed to write to file '" + other 1167 + "': " + e); 1168 } finally { 1169 1170 // Reset all state changes 1171 if (pwCsv != null) { 1172 pwCsv.close(); 1173 } 1174 1175 pwCsv = null; 1176 csvColDelim = null; 1177 csvRowDelim = null; 1178 } 1179 1180 return; 1181 1182 case 'd' : 1183 if (arg1.length() == 2) { 1184 listTables(arg1.charAt(1), other); 1185 1186 return; 1187 } 1188 1189 if (arg1.length() == 1 && other != null) { 1190 int space = other.indexOf(' '); 1191 1192 if (space < 0) { 1193 describe(other, null); 1194 } else { 1195 describe(other.substring(0, space), 1196 other.substring(space + 1).trim()); 1197 } 1198 1199 return; 1200 } 1201 1202 throw new BadSpecial("Describe commands must be like " 1203 + "'\\dX' or like '\\d OBJECTNAME'."); 1204 case 'o' : 1205 if (other == null) { 1206 if (pwQuery == null) { 1207 throw new BadSpecial( 1208 "There is no query output file to close"); 1209 } 1210 1211 closeQueryOutputStream(); 1212 1213 return; 1214 } 1215 1216 if (pwQuery != null) { 1217 stdprintln( 1218 "Closing current query output file and opening " 1219 + "new one"); 1220 closeQueryOutputStream(); 1221 } 1222 1223 try { 1224 pwQuery = new PrintWriter( 1225 new OutputStreamWriter( 1226 new FileOutputStream(other, true), charset)); 1227 1228 /* Opening in append mode, so it's possible that we will 1229 * be adding superfluous <HTML> and <BODY> tages. 1230 * I think that browsers can handle that */ 1231 pwQuery.println((htmlMode ? "<HTML>\n<!--" 1232 : "#") + " " 1233 + (new java.util.Date()) 1234 + ". Query output from " 1235 + getClass().getName() 1236 + (htmlMode 1237 ? ". -->\n\n<BODY>" 1238 : ".\n")); 1239 pwQuery.flush(); 1240 } catch (Exception e) { 1241 throw new BadSpecial("Failed to write to file '" + other 1242 + "': " + e); 1243 } 1244 1245 return; 1246 1247 case 'w' : 1248 if (other == null) { 1249 throw new BadSpecial( 1250 "You must supply a destination file name"); 1251 } 1252 1253 if (commandFromHistory(0).length() == 0) { 1254 throw new BadSpecial("Empty command in buffer"); 1255 } 1256 1257 try { 1258 PrintWriter pw = new PrintWriter( 1259 new OutputStreamWriter( 1260 new FileOutputStream(other, true), charset)); 1261 1262 pw.println(commandFromHistory(0) + ';'); 1263 pw.flush(); 1264 pw.close(); 1265 } catch (Exception e) { 1266 throw new BadSpecial("Failed to append to file '" + other 1267 + "': " + e); 1268 } 1269 1270 return; 1271 1272 case 'i' : 1273 if (other == null) { 1274 throw new BadSpecial("You must supply an SQL file name"); 1275 } 1276 1277 try { 1278 SqlFile sf = new SqlFile(new File(other), false, 1279 userVars); 1280 1281 sf.recursed = true; 1282 1283 // Share the possiblyUncommitted state 1284 sf.possiblyUncommitteds = possiblyUncommitteds; 1285 sf.plMode = plMode; 1286 1287 sf.execute(curConn, continueOnError); 1288 } catch (ContinueException ce) { 1289 throw ce; 1290 } catch (BreakException be) { 1291 String beMessage = be.getMessage(); 1292 1293 if (beMessage != null &&!beMessage.equals("file")) { 1294 throw be; 1295 } 1296 } catch (QuitNow qe) { 1297 throw qe; 1298 } catch (Exception e) { 1299 throw new BadSpecial("Failed to execute SQL from file '" 1300 + other + "': " + e.getMessage()); 1301 } 1302 1303 return; 1304 1305 case 'p' : 1306 if (other == null) { 1307 stdprintln(true); 1308 } else { 1309 stdprintln(other, true); 1310 } 1311 1312 return; 1313 1314 case 'a' : 1315 if (other != null) { 1316 curConn.setAutoCommit( 1317 Boolean.valueOf(other).booleanValue()); 1318 } 1319 1320 stdprintln("Auto-commit is set to: " 1321 + curConn.getAutoCommit()); 1322 1323 return; 1324 1325 case 'b' : 1326 if (arg1.length() == 1) { 1327 fetchBinary = true; 1328 1329 return; 1330 } 1331 1332 if (arg1.charAt(1) == 'p') { 1333 doPrepare = true; 1334 1335 return; 1336 } 1337 1338 if ((arg1.charAt(1) != 'd' && arg1.charAt(1) != 'l') 1339 || other == null) { 1340 throw new BadSpecial("Malformatted binary command"); 1341 } 1342 1343 File file = new File(other); 1344 1345 try { 1346 if (arg1.charAt(1) == 'd') { 1347 dump(file); 1348 } else { 1349 load(file); 1350 } 1351 } catch (Exception e) { 1352 throw new BadSpecial( 1353 "Failed to load/dump binary data to file '" + other 1354 + "'"); 1355 } 1356 1357 return; 1358 1359 case '*' : 1360 case 'c' : 1361 if (other != null) { 1362 1363 // But remember that we have to abort on some I/O errors. 1364 continueOnError = Boolean.valueOf(other).booleanValue(); 1365 } 1366 1367 stdprintln("Continue-on-error is set to: " + continueOnError); 1368 1369 return; 1370 1371 case 's' : 1372 showHistory(); 1373 1374 return; 1375 1376 case '-' : 1377 int commandsAgo = 0; 1378 String numStr; 1379 boolean executeMode = arg1.charAt(arg1.length() - 1) == ';'; 1380 1381 if (executeMode) { 1382 1383 // Trim off terminating ';' 1384 arg1 = arg1.substring(0, arg1.length() - 1); 1385 } 1386 1387 numStr = (arg1.length() == 1) ? null 1388 : arg1.substring(1, 1389 arg1.length()); 1390 1391 if (numStr == null) { 1392 commandsAgo = 0; 1393 } else { 1394 try { 1395 commandsAgo = Integer.parseInt(numStr); 1396 } catch (NumberFormatException nfe) { 1397 throw new BadSpecial("Malformatted command number"); 1398 } 1399 } 1400 1401 setBuf(commandFromHistory(commandsAgo)); 1402 1403 if (executeMode) { 1404 processBuffer(";"); 1405 } else { 1406 stdprintln( 1407 "RESTORED following command to buffer. Enter \":?\" " 1408 + "to see buffer commands:\n" 1409 + commandFromHistory(0)); 1410 } 1411 1412 return; 1413 1414 case '?' : 1415 stdprintln(HELP_TEXT); 1416 1417 return; 1418 1419 case '!' : 1420 InputStream stream; 1421 byte[] ba = new byte[1024]; 1422 String extCommand = ((arg1.length() == 1) ? "" 1423 : arg1.substring(1)) + ((arg1.length() > 1 && other != null) 1424 ? " " 1425 : "") + ((other == null) 1426 ? "" 1427 : other); 1428 1429 try { 1430 Process proc = Runtime.getRuntime().exec(extCommand); 1431 1432 proc.getOutputStream().close(); 1433 1434 int i; 1435 1436 stream = proc.getInputStream(); 1437 1438 while ((i = stream.read(ba)) > 0) { 1439 stdprint(new String(ba, 0, i)); 1440 } 1441 1442 stream.close(); 1443 1444 stream = proc.getErrorStream(); 1445 1446 while ((i = stream.read(ba)) > 0) { 1447 errprint(new String(ba, 0, i)); 1448 } 1449 1450 stream.close(); 1451 1452 if (proc.waitFor() != 0) { 1453 throw new BadSpecial("External command failed: '" 1454 + extCommand + "'"); 1455 } 1456 } catch (Exception e) { 1457 throw new BadSpecial("Failed to execute command '" 1458 + extCommand + "': " + e); 1459 } 1460 1461 return; 1462 1463 case '.' : 1464 chunking = true; 1465 1466 if (interactive) { 1467 stdprintln("Enter RAW SQL. No \\, :, * commands. " 1468 + "End with a line containing only \".\":"); 1469 } 1470 1471 return; 1472 } 1473 1474 throw new BadSpecial("Unknown Special Command"); 1475 } 1476 1477 private static final char[] nonVarChars = { 1478 ' ', '\t', '=', '}', '\n', '\r' 1479 }; 1480 1481 /** 1482 * Returns index specifying 1 past end of a variable name. 1483 * 1484 * @param inString String containing a variable name 1485 * @param startIndex Index within inString where the variable name begins 1486 * @returns Index within inString, 1 past end of the variable name 1487 */ 1488 static int pastName(String inString, int startIndex) { 1489 1490 String workString = inString.substring(startIndex); 1491 int e = inString.length(); // Index 1 past end of var name. 1492 int nonVarIndex; 1493 1494 for (int i = 0; i < nonVarChars.length; i++) { 1495 nonVarIndex = workString.indexOf(nonVarChars[i]); 1496 1497 if (nonVarIndex > -1 && nonVarIndex < e) { 1498 e = nonVarIndex; 1499 } 1500 } 1501 1502 return startIndex + e; 1503 } 1504 1505 /** 1506 * Deference PL variables. 1507 * 1508 * @throws SQLException This is really an inappropriate exception 1509 * type. Only using it because I don't have time to do things properly. 1510 */ 1511 @SuppressWarnings("unused") 1512 private String dereference(String inString, 1513 boolean permitAlias) throws SQLException { 1514 1515 String varName, varValue; 1516 StringBuffer expandBuffer = new StringBuffer(inString); 1517 int b, e; // begin and end of name. end really 1 PAST name 1518 int nonVarIndex; 1519 1520 if (permitAlias && inString.trim().charAt(0) == '/') { 1521 int slashIndex = inString.indexOf('/'); 1522 1523 e = pastName(inString.substring(slashIndex + 1), 0); 1524 1525 // In this case, e is the exact length of the var name. 1526 if (e < 1) { 1527 throw new SQLException("Malformed PL alias use"); 1528 } 1529 1530 varName = inString.substring(slashIndex + 1, slashIndex + 1 + e); 1531 varValue = (String) userVars.get(varName); 1532 1533 if (varValue == null) { 1534 throw new SQLException("Undefined PL variable: " + varName); 1535 } 1536 1537 expandBuffer.replace(slashIndex, slashIndex + 1 + e, 1538 (String) userVars.get(varName)); 1539 } 1540 1541 String s; 1542 1543 while (true) { 1544 s = expandBuffer.toString(); 1545 b = s.indexOf("*{"); 1546 1547 if (b < 0) { 1548 1549 // No more unexpanded variable uses 1550 break; 1551 } 1552 1553 e = s.indexOf('}', b + 2); 1554 1555 if (e == b + 2) { 1556 throw new SQLException("Empty PL variable name"); 1557 } 1558 1559 if (e < 0) { 1560 throw new SQLException("Unterminated PL variable name"); 1561 } 1562 1563 varName = s.substring(b + 2, e); 1564 1565 if (!userVars.containsKey(varName)) { 1566 throw new SQLException("Use of undefined PL variable: " 1567 + varName); 1568 } 1569 1570 expandBuffer.replace(b, e + 1, (String) userVars.get(varName)); 1571 } 1572 1573 return expandBuffer.toString(); 1574 } 1575 1576 public boolean plMode = false; 1577 1578 // PL variable name currently awaiting query output. 1579 private String fetchingVar = null; 1580 private boolean silentFetch = false; 1581 private boolean fetchBinary = false; 1582 1583 /** 1584 * Process a Process Language Command. 1585 * Nesting not supported yet. 1586 * 1587 * @param inString Complete command, less the leading '\' character. 1588 * @throws BadSpecial Runtime error() 1589 */ 1590 @SuppressWarnings("unchecked") 1591 private void processPL(String inString) 1592 throws BadSpecial, SqlToolError, SQLException { 1593 1594 if (inString.length() < 1) { 1595 plMode = true; 1596 1597 stdprintln("PL variable expansion mode is now on"); 1598 1599 return; 1600 } 1601 1602 if (inString.charAt(0) == '?') { 1603 stdprintln(PL_HELP_TEXT); 1604 1605 return; 1606 } 1607 1608 if (plMode) { 1609 inString = dereference(inString, false); 1610 } 1611 1612 StringTokenizer toker = new StringTokenizer(inString); 1613 String arg1 = toker.nextToken(); 1614 String[] tokenArray = null; 1615 1616 // If user runs any PL command, we turn PL mode on. 1617 plMode = true; 1618 1619 if (userVars == null) { 1620 userVars = new HashMap(); 1621 } 1622 1623 if (arg1.equals("end")) { 1624 throw new BadSpecial("PL end statements may only occur inside of " 1625 + "a PL block"); 1626 } 1627 1628 if (arg1.equals("continue")) { 1629 if (toker.hasMoreTokens()) { 1630 String s = toker.nextToken("").trim(); 1631 1632 if (s.equals("foreach") || s.equals("while")) { 1633 throw new ContinueException(s); 1634 } else { 1635 throw new BadSpecial( 1636 "Bad continue statement." 1637 + "You may use no argument or one of 'foreach', " 1638 + "'while'"); 1639 } 1640 } 1641 1642 throw new ContinueException(); 1643 } 1644 1645 if (arg1.equals("break")) { 1646 if (toker.hasMoreTokens()) { 1647 String s = toker.nextToken("").trim(); 1648 1649 if (s.equals("foreach") || s.equals("if") 1650 || s.equals("while") || s.equals("file")) { 1651 throw new BreakException(s); 1652 } else { 1653 throw new BadSpecial( 1654 "Bad break statement." 1655 + "You may use no argument or one of 'foreach', " 1656 + "'if', 'while', 'file'"); 1657 } 1658 } 1659 1660 throw new BreakException(); 1661 } 1662 1663 if (arg1.equals("list") || arg1.equals("listvalue")) { 1664 String s; 1665 boolean doValues = (arg1.equals("listvalue")); 1666 1667 if (toker.countTokens() == 0) { 1668 stdprint(formatNicely(userVars, doValues)); 1669 } else { 1670 tokenArray = getTokenArray(toker.nextToken("")); 1671 1672 if (doValues) { 1673 stdprintln("The outermost parentheses are not part of " 1674 + "the values."); 1675 } else { 1676 stdprintln("Showing variable names and length of values " 1677 + "(use 'listvalue' to see values)."); 1678 } 1679 1680 for (int i = 0; i < tokenArray.length; i++) { 1681 s = (String) userVars.get(tokenArray[i]); 1682 1683 stdprintln(" " + tokenArray[i] + ": " 1684 + (doValues ? ("(" + s + ')') 1685 : Integer.toString(s.length()))); 1686 } 1687 } 1688 1689 return; 1690 } 1691 1692 if (arg1.equals("dump") || arg1.equals("load")) { 1693 if (toker.countTokens() != 2) { 1694 throw new BadSpecial("Malformatted PL dump/load command"); 1695 } 1696 1697 String varName = toker.nextToken(); 1698 File file = new File(toker.nextToken()); 1699 1700 try { 1701 if (arg1.equals("dump")) { 1702 dump(varName, file); 1703 } else { 1704 load(varName, file); 1705 } 1706 } catch (Exception e) { 1707 throw new BadSpecial("Failed to dump/load variable '" 1708 + varName + "' to file '" + file + "'"); 1709 } 1710 1711 return; 1712 } 1713 1714 if (arg1.equals("prepare")) { 1715 if (toker.countTokens() != 1) { 1716 throw new BadSpecial("Malformatted prepare command"); 1717 } 1718 1719 String s = toker.nextToken(); 1720 1721 if (userVars.get(s) == null) { 1722 throw new SQLException("Use of unset PL variable: " + s); 1723 } 1724 1725 prepareVar = s; 1726 doPrepare = true; 1727 1728 return; 1729 } 1730 1731 if (arg1.equals("foreach")) { 1732 if (toker.countTokens() < 2) { 1733 throw new BadSpecial("Malformatted PL foreach command (1)"); 1734 } 1735 1736 String varName = toker.nextToken(); 1737 String parenExpr = toker.nextToken("").trim(); 1738 1739 if (parenExpr.length() < 2 || parenExpr.charAt(0) != '(' 1740 || parenExpr.charAt(parenExpr.length() - 1) != ')') { 1741 throw new BadSpecial("Malformatted PL foreach command (2)"); 1742 } 1743 1744 String[] values = getTokenArray(parenExpr.substring(1, 1745 parenExpr.length() - 1)); 1746 File tmpFile = null; 1747 String varVal; 1748 1749 try { 1750 tmpFile = plBlockFile("foreach"); 1751 } catch (IOException ioe) { 1752 throw new BadSpecial( 1753 "Failed to write given PL block temp file: " + ioe); 1754 } 1755 1756 String origval = (String) userVars.get(varName); 1757 1758 try { 1759 SqlFile sf; 1760 1761 for (int i = 0; i < values.length; i++) { 1762 try { 1763 varVal = values[i]; 1764 1765 userVars.put(varName, varVal); 1766 1767 sf = new SqlFile(tmpFile, false, userVars); 1768 sf.plMode = true; 1769 sf.recursed = true; 1770 1771 // Share the possiblyUncommitted state 1772 sf.possiblyUncommitteds = possiblyUncommitteds; 1773 1774 sf.execute(curConn, continueOnError); 1775 } catch (ContinueException ce) { 1776 String ceMessage = ce.getMessage(); 1777 1778 if (ceMessage != null 1779 &&!ceMessage.equals("foreach")) { 1780 throw ce; 1781 } 1782 } 1783 } 1784 } catch (BreakException be) { 1785 String beMessage = be.getMessage(); 1786 1787 if (beMessage != null &&!beMessage.equals("foreach")) { 1788 throw be; 1789 } 1790 } catch (QuitNow qe) { 1791 throw qe; 1792 } catch (Exception e) { 1793 throw new BadSpecial("Failed to execute SQL from PL block. " 1794 + e.getMessage()); 1795 } 1796 1797 if (origval == null) { 1798 userVars.remove(varName); 1799 } else { 1800 userVars.put(varName, origval); 1801 } 1802 1803 if (tmpFile != null &&!tmpFile.delete()) { 1804 throw new BadSpecial( 1805 "Error occurred while trying to remove temp file '" 1806 + tmpFile + "'"); 1807 } 1808 1809 return; 1810 } 1811 1812 if (arg1.equals("if")) { 1813 if (toker.countTokens() < 1) { 1814 throw new BadSpecial("Malformatted PL if command (1)"); 1815 } 1816 1817 String parenExpr = toker.nextToken("").trim(); 1818 1819 if (parenExpr.length() < 2 || parenExpr.charAt(0) != '(' 1820 || parenExpr.charAt(parenExpr.length() - 1) != ')') { 1821 throw new BadSpecial("Malformatted PL if command (2)"); 1822 } 1823 1824 String[] values = getTokenArray(parenExpr.substring(1, 1825 parenExpr.length() - 1)); 1826 File tmpFile = null; 1827 1828 try { 1829 tmpFile = plBlockFile("if"); 1830 } catch (IOException ioe) { 1831 throw new BadSpecial( 1832 "Failed to write given PL block temp file: " + ioe); 1833 } 1834 1835 try { 1836 if (eval(values)) { 1837 SqlFile sf = new SqlFile(tmpFile, false, userVars); 1838 1839 sf.plMode = true; 1840 sf.recursed = true; 1841 1842 // Share the possiblyUncommitted state 1843 sf.possiblyUncommitteds = possiblyUncommitteds; 1844 1845 sf.execute(curConn, continueOnError); 1846 } 1847 } catch (BreakException be) { 1848 String beMessage = be.getMessage(); 1849 1850 if (beMessage == null ||!beMessage.equals("if")) { 1851 throw be; 1852 } 1853 } catch (ContinueException ce) { 1854 throw ce; 1855 } catch (QuitNow qe) { 1856 throw qe; 1857 } catch (BadSpecial bs) { 1858 throw new BadSpecial("Malformatted PL if command (3): " + bs); 1859 } catch (Exception e) { 1860 throw new BadSpecial("Failed to execute SQL from PL block. " 1861 + e.getMessage()); 1862 } 1863 1864 if (tmpFile != null &&!tmpFile.delete()) { 1865 throw new BadSpecial( 1866 "Error occurred while trying to remove temp file '" 1867 + tmpFile + "'"); 1868 } 1869 1870 return; 1871 } 1872 1873 if (arg1.equals("while")) { 1874 if (toker.countTokens() < 1) { 1875 throw new BadSpecial("Malformatted PL while command (1)"); 1876 } 1877 1878 String parenExpr = toker.nextToken("").trim(); 1879 1880 if (parenExpr.length() < 2 || parenExpr.charAt(0) != '(' 1881 || parenExpr.charAt(parenExpr.length() - 1) != ')') { 1882 throw new BadSpecial("Malformatted PL while command (2)"); 1883 } 1884 1885 String[] values = getTokenArray(parenExpr.substring(1, 1886 parenExpr.length() - 1)); 1887 File tmpFile = null; 1888 1889 try { 1890 tmpFile = plBlockFile("while"); 1891 } catch (IOException ioe) { 1892 throw new BadSpecial( 1893 "Failed to write given PL block temp file: " + ioe); 1894 } 1895 1896 try { 1897 SqlFile sf; 1898 1899 while (eval(values)) { 1900 try { 1901 sf = new SqlFile(tmpFile, false, userVars); 1902 sf.recursed = true; 1903 1904 // Share the possiblyUncommitted state 1905 sf.possiblyUncommitteds = possiblyUncommitteds; 1906 sf.plMode = true; 1907 1908 sf.execute(curConn, continueOnError); 1909 } catch (ContinueException ce) { 1910 String ceMessage = ce.getMessage(); 1911 1912 if (ceMessage != null &&!ceMessage.equals("while")) { 1913 throw ce; 1914 } 1915 } 1916 } 1917 } catch (BreakException be) { 1918 String beMessage = be.getMessage(); 1919 1920 if (beMessage != null &&!beMessage.equals("while")) { 1921 throw be; 1922 } 1923 } catch (QuitNow qe) { 1924 throw qe; 1925 } catch (BadSpecial bs) { 1926 throw new BadSpecial("Malformatted PL while command (3): " 1927 + bs); 1928 } catch (Exception e) { 1929 throw new BadSpecial("Failed to execute SQL from PL block. " 1930 + e.getMessage()); 1931 } 1932 1933 if (tmpFile != null &&!tmpFile.delete()) { 1934 throw new BadSpecial( 1935 "Error occurred while trying to remove temp file '" 1936 + tmpFile + "'"); 1937 } 1938 1939 return; 1940 } 1941 1942 /* Since we don't want to permit both "* VARNAME = X" and 1943 * "* VARNAME=X" (i.e., whitespace is OPTIONAL in both positions), 1944 * we can't use the Tokenzier. Therefore, start over again with 1945 * the inString. */ 1946 toker = null; 1947 1948 int index = pastName(inString, 0); 1949 int inLength = inString.length(); 1950 String varName = inString.substring(0, index); 1951 1952 while (index + 1 < inLength 1953 && (inString.charAt(index) == ' ' 1954 || inString.charAt(index) == '\t')) { 1955 index++; 1956 } 1957 1958 // index now set to the next non-whitespace AFTER the var name. 1959 if (index + 1 > inLength) { 1960 throw new BadSpecial("Unterminated PL variable definition"); 1961 } 1962 1963 char operator = inString.charAt(index); 1964 String remainder = inString.substring(index + 1); 1965 1966 switch (inString.charAt(index)) { 1967 1968 case '_' : 1969 silentFetch = true; 1970 case '~' : 1971 if (remainder.length() > 0) { 1972 throw new BadSpecial( 1973 "PL ~/_ set commands take no other args"); 1974 } 1975 1976 userVars.remove(varName); 1977 1978 fetchingVar = varName; 1979 1980 return; 1981 1982 case '=' : 1983 if (fetchingVar != null && fetchingVar.equals(varName)) { 1984 fetchingVar = null; 1985 } 1986 1987 if (remainder.length() > 0) { 1988 userVars.put(varName, 1989 inString.substring(index + 1).trim()); 1990 } else { 1991 userVars.remove(varName); 1992 } 1993 1994 return; 1995 } 1996 1997 throw new BadSpecial("Unknown PL command (3)"); 1998 } 1999 2000 /* 2001 * Read a PL block into a new temp file. 2002 * 2003 * WARNING!!! foreach blocks are not yet smart about comments 2004 * and strings. We just look for a line beginning with a PL "end" 2005 * command without worrying about comments or quotes (for now). 2006 * 2007 * WARNING!!! This is very rudimentary. 2008 * Users give up all editing and feedback capabilities while 2009 * in the foreach loop. 2010 * A better solution would be to pass current input stream to a 2011 * new SqlFile.execute() with a mode whereby commands are written 2012 * to a separate history but not executed. 2013 */ 2014 private File plBlockFile(String type) throws IOException, SqlToolError { 2015 2016 String s; 2017 StringTokenizer toker; 2018 2019 // Have already read the if/while/foreach statement, so we are already 2020 // at nest level 1. When we reach nestlevel 1 (read 1 net "end" 2021 // statement), we're at level 0 and return. 2022 int nestlevel = 1; 2023 String curPlCommand; 2024 2025 if (type == null 2026 || ((!type.equals("foreach")) && (!type.equals("if")) 2027 && (!type.equals("while")))) { 2028 throw new RuntimeException( 2029 "Assertion failed. Unsupported PL block type: " + type); 2030 } 2031 2032 File tmpFile = File.createTempFile("sqltool-", ".sql"); 2033 PrintWriter pw = new PrintWriter( 2034 new OutputStreamWriter(new FileOutputStream(tmpFile), charset)); 2035 2036 pw.println("/* " + (new java.util.Date()) + ". " 2037 + getClass().getName() + " PL block. */\n"); 2038 2039 while (true) { 2040 s = br.readLine(); 2041 2042 if (s == null) { 2043 errprintln("Unterminated '" + type + "' PL block"); 2044 2045 throw new SqlToolError("Unterminated '" + type 2046 + "' PL block"); 2047 } 2048 2049 curLinenum++; 2050 2051 if (s.trim().length() > 1 && s.trim().charAt(0) == '*') { 2052 toker = new StringTokenizer(s.trim().substring(1)); 2053 curPlCommand = toker.nextToken(); 2054 2055 // PL COMMAND of some sort. 2056 if (curPlCommand.equals(type)) { 2057 nestlevel++; 2058 } else if (curPlCommand.equals("end")) { 2059 if (toker.countTokens() < 1) { 2060 errprintln("PL end statement requires arg of " 2061 + "'foreach' or 'if' or 'while' (1)"); 2062 2063 throw new SqlToolError( 2064 "PL end statement requires arg " 2065 + " of 'foreach' or 'if' or 'while' (1)"); 2066 } 2067 2068 String inType = toker.nextToken(); 2069 2070 if (inType.equals(type)) { 2071 nestlevel--; 2072 2073 if (nestlevel < 1) { 2074 break; 2075 } 2076 } 2077 2078 if ((!inType.equals("foreach")) && (!inType.equals("if")) 2079 && (!inType.equals("while"))) { 2080 errprintln("PL end statement requires arg of " 2081 + "'foreach' or 'if' or 'while' (2)"); 2082 2083 throw new SqlToolError( 2084 "PL end statement requires arg of " 2085 + "'foreach' or 'if' or 'while' (2)"); 2086 } 2087 } 2088 } 2089 2090 pw.println(s); 2091 } 2092 2093 pw.flush(); 2094 pw.close(); 2095 2096 return tmpFile; 2097 } 2098 2099 /** 2100 * Wrapper methods so don't need to call x(..., false) in most cases. 2101 */ 2102 private void stdprintln() { 2103 stdprintln(false); 2104 } 2105 2106 private void stdprint(String s) { 2107 stdprint(s, false); 2108 } 2109 2110 private void stdprintln(String s) { 2111 stdprintln(s, false); 2112 } 2113 2114 /** 2115 * Encapsulates normal output. 2116 * 2117 * Conditionally HTML-ifies output. 2118 */ 2119 private void stdprintln(boolean queryOutput) { 2120 2121 if (htmlMode) { 2122 psStd.println("<BR>"); 2123 } else { 2124 psStd.println(); 2125 } 2126 2127 if (queryOutput && pwQuery != null) { 2128 if (htmlMode) { 2129 pwQuery.println("<BR>"); 2130 } else { 2131 pwQuery.println(); 2132 } 2133 2134 pwQuery.flush(); 2135 } 2136 } 2137 2138 /** 2139 * Encapsulates error output. 2140 * 2141 * Conditionally HTML-ifies error output. 2142 */ 2143 private void errprint(String s) { 2144 2145 psErr.print(htmlMode 2146 ? ("<DIV style='color:white; background: red; " 2147 + "font-weight: bold'>" + s + "</DIV>") 2148 : s); 2149 } 2150 2151 /** 2152 * Encapsulates error output. 2153 * 2154 * Conditionally HTML-ifies error output. 2155 */ 2156 private void errprintln(String s) { 2157 2158 psErr.println(htmlMode 2159 ? ("<DIV style='color:white; background: red; " 2160 + "font-weight: bold'>" + s + "</DIV>") 2161 : s); 2162 } 2163 2164 /** 2165 * Encapsulates normal output. 2166 * 2167 * Conditionally HTML-ifies output. 2168 */ 2169 private void stdprint(String s, boolean queryOutput) { 2170 2171 psStd.print(htmlMode ? ("<P>" + s + "</P>") 2172 : s); 2173 2174 if (queryOutput && pwQuery != null) { 2175 pwQuery.print(htmlMode ? ("<P>" + s + "</P>") 2176 : s); 2177 pwQuery.flush(); 2178 } 2179 } 2180 2181 /** 2182 * Encapsulates normal output. 2183 * 2184 * Conditionally HTML-ifies output. 2185 */ 2186 private void stdprintln(String s, boolean queryOutput) { 2187 2188 psStd.println(htmlMode ? ("<P>" + s + "</P>") 2189 : s); 2190 2191 if (queryOutput && pwQuery != null) { 2192 pwQuery.println(htmlMode ? ("<P>" + s + "</P>") 2193 : s); 2194 pwQuery.flush(); 2195 } 2196 } 2197 2198 // Just because users may be used to seeing "[null]" in normal 2199 // SqlFile output, we use the same default value for null in CSV 2200 // files, but this CSV null representation can be changed to anything. 2201 private static final String DEFAULT_NULL_REP = "[null]"; 2202 private static final String DEFAULT_ROW_DELIM = 2203 System.getProperty("line.separator"); 2204 private static final String DEFAULT_COL_DELIM = "|"; 2205 private static final int DEFAULT_ELEMENT = 0, 2206 HSQLDB_ELEMENT = 1, 2207 ORACLE_ELEMENT = 2 2208 ; 2209 2210 // These do not specify order listed, just inclusion. 2211 private static final int[] listMDSchemaCols = { 1 }; 2212 private static final int[] listMDIndexCols = { 2213 2, 6, 3, 9, 4, 10, 11 2214 }; 2215 2216 /** Column numbering starting at 1. */ 2217 private static final int[][] listMDTableCols = { 2218 { 2219 2, 3 2220 }, // Default 2221 { 2222 2, 3 2223 }, // HSQLDB 2224 { 2225 2, 3 2226 }, // Oracle 2227 }; 2228 2229 /** 2230 * SYS and SYSTEM are the only base system accounts in Oracle, however, 2231 * from an empirical perspective, all of these other accounts are 2232 * system accounts because <UL> 2233 * <LI> they are hidden from the casual user 2234 * <LI> they are created by the installer at installation-time 2235 * <LI> they are used automatically by the Oracle engine when the 2236 * specific Oracle sub-product is used 2237 * <LI> the accounts should not be <I>messed with</I> by database users 2238 * <LI> the accounts should certainly not be used if the specific 2239 * Oracle sub-product is going to be used. 2240 * </UL> 2241 * 2242 * General advice: If you aren't going to use an Oracle sub-product, 2243 * then <B>don't install it!</B> 2244 * Don't blindly accept default when running OUI. 2245 * 2246 * If users also see accounts that they didn't create with names like 2247 * SCOTT, ADAMS, JONES, CLARK, BLAKE, OE, PM, SH, QS, QS_*, these 2248 * contain sample data and the schemas can safely be removed. 2249 */ 2250 private static final String[] oracleSysSchemas = { 2251 "SYS", "SYSTEM", "OUTLN", "DBSNMP", "OUTLN", "MDSYS", "ORDSYS", 2252 "ORDPLUGINS", "CTXSYS", "DSSYS", "PERFSTAT", "WKPROXY", "WKSYS", 2253 "WMSYS", "XDB", "ANONYMOUS", "ODM", "ODM_MTR", "OLAPSYS", "TRACESVR", 2254 "REPADMIN" 2255 }; 2256 2257 /** 2258 * Lists available database tables. 2259 * 2260 * When a filter is given, we assume that there are no lower-case 2261 * characters in the object names (which would require "quotes" when 2262 * creating them). 2263 * 2264 * @throws BadSpecial 2265 */ 2266 private void listTables(char c, String inFilter) throws BadSpecial { 2267 2268 String schema = null; 2269 int[] listSet = null; 2270 String[] types = null; 2271 2272 /** For workaround for \T for Oracle */ 2273 String[] additionalSchemas = null; 2274 2275 /** This is for specific non-getTable() queries */ 2276 Statement statement = null; 2277 ResultSet rs = null; 2278 String narrower = ""; 2279 /* 2280 * Doing case-sensitive filters now, for greater portability. 2281 String filter = ((inFilter == null) 2282 ? null : inFilter.toUpperCase()); 2283 */ 2284 String filter = inFilter; 2285 2286 try { 2287 DatabaseMetaData md = curConn.getMetaData(); 2288 String dbProductName = md.getDatabaseProductName(); 2289 2290 //System.err.println("DB NAME = (" + dbProductName + ')'); 2291 // Database-specific table filtering. 2292 @SuppressWarnings("unused") 2293 String excludePrefix = null; 2294 2295 /* 3 Types of actions: 2296 * 1) Special handling. Return from the "case" block directly. 2297 * 2) Execute a specific query. Set statement in the "case". 2298 * 3) Otherwise, set filter info for dbmd.getTable() in the 2299 * "case". 2300 */ 2301 types = new String[1]; 2302 2303 switch (c) { 2304 2305 case '*' : 2306 types = null; 2307 break; 2308 2309 case 'S' : 2310 if (dbProductName.indexOf("Oracle") > -1) { 2311 System.err.println( 2312 "*** WARNING:\n*** Listing tables in " 2313 + "system-supplied schemas since\n*** Oracle" 2314 + "(TM) doesn't return a JDBC system table list."); 2315 2316 types[0] = "TABLE"; 2317 schema = "SYS"; 2318 additionalSchemas = oracleSysSchemas; 2319 } else { 2320 types[0] = "SYSTEM TABLE"; 2321 } 2322 break; 2323 2324 case 's' : 2325 if (dbProductName.indexOf("HSQL") > -1) { 2326 2327 // HSQLDB does not consider Sequences as "tables", 2328 // hence we do not list them in 2329 // DatabaseMetaData.getTables(). 2330 if (filter != null 2331 && filter.charAt(filter.length() - 1) 2332 == '.') { 2333 narrower = 2334 "\nWHERE sequence_schema = '" 2335 + filter.substring(0, filter.length() - 1) 2336 + "'"; 2337 filter = null; 2338 } 2339 2340 statement = curConn.createStatement(); 2341 2342 statement.execute( 2343 "SELECT sequence_schema, sequence_name FROM " 2344 + "information_schema.system_sequences" 2345 + narrower); 2346 } else { 2347 types[0] = "SEQUENCE"; 2348 } 2349 break; 2350 2351 case 'r' : 2352 if (dbProductName.indexOf("HSQL") > -1) { 2353 statement = curConn.createStatement(); 2354 2355 statement.execute( 2356 "SELECT authorization_name FROM " 2357 + "information_schema.system_authorizations\n" 2358 + "WHERE authorization_type = 'ROLE'\n" 2359 + "ORDER BY authorization_name"); 2360 } else if (dbProductName.indexOf( 2361 "Adaptive Server Enterprise") > -1) { 2362 2363 // This is the basic Sybase server. Sybase also has 2364 // their "Anywhere", ASA (for embedded), and replication 2365 // databases, but I don't know the Metadata strings for 2366 // those. 2367 statement = curConn.createStatement(); 2368 2369 statement.execute( 2370 "SELECT name FROM syssrvroles ORDER BY name"); 2371 } else { 2372 throw new BadSpecial( 2373 "SqlFile does not yet support " 2374 + "\\dr for your database vendor"); 2375 } 2376 break; 2377 2378 case 'u' : 2379 if (dbProductName.indexOf("HSQL") > -1) { 2380 statement = curConn.createStatement(); 2381 2382 statement.execute( 2383 "SELECT user, admin FROM " 2384 + "information_schema.system_users\n" 2385 + "ORDER BY user"); 2386 } else if (dbProductName.indexOf("Oracle") > -1) { 2387 statement = curConn.createStatement(); 2388 2389 statement.execute( 2390 "SELECT username, created FROM all_users " 2391 + "ORDER BY username"); 2392 } else if (dbProductName.indexOf("PostgreSQL") > -1) { 2393 statement = curConn.createStatement(); 2394 2395 statement.execute( 2396 "SELECT usename, usesuper FROM pg_catalog.pg_user " 2397 + "ORDER BY usename"); 2398 } else if (dbProductName.indexOf( 2399 "Adaptive Server Enterprise") > -1) { 2400 2401 // This is the basic Sybase server. Sybase also has 2402 // their "Anywhere", ASA (for embedded), and replication 2403 // databases, but I don't know the Metadata strings for 2404 // those. 2405 statement = curConn.createStatement(); 2406 2407 statement.execute( 2408 "SELECT name, accdate, fullname FROM syslogins " 2409 + "ORDER BY name"); 2410 } else { 2411 throw new BadSpecial( 2412 "SqlFile does not yet support " 2413 + "\\du for your database vendor"); 2414 } 2415 break; 2416 2417 case 'a' : 2418 if (dbProductName.indexOf("HSQL") > -1) { 2419 2420 // HSQLDB Aliases are not the same things as the 2421 // aliases listed in DatabaseMetaData.getTables(). 2422 if (filter != null 2423 && filter.charAt(filter.length() - 1) 2424 == '.') { 2425 narrower = 2426 "\nWHERE alias_schem = '" 2427 + filter.substring(0, filter.length() - 1) 2428 + "'"; 2429 filter = null; 2430 } 2431 2432 statement = curConn.createStatement(); 2433 2434 statement.execute( 2435 "SELECT alias_schem, alias FROM " 2436 + "information_schema.system_aliases" + narrower); 2437 } else { 2438 types[0] = "ALIAS"; 2439 } 2440 break; 2441 2442 case 't' : 2443 excludeSysSchemas = (dbProductName.indexOf("Oracle") 2444 > -1); 2445 types[0] = "TABLE"; 2446 break; 2447 2448 case 'v' : 2449 types[0] = "VIEW"; 2450 break; 2451 2452 case 'n' : 2453 rs = md.getSchemas(); 2454 2455 if (rs == null) { 2456 throw new BadSpecial( 2457 "Failed to get metadata from database"); 2458 } 2459 2460 displayResultSet(null, rs, listMDSchemaCols, filter); 2461 2462 return; 2463 2464 case 'i' : 2465 2466 // Some databases require to specify table, some don't. 2467 /* 2468 if (filter == null) { 2469 throw new BadSpecial("You must specify the index's " 2470 + "table as argument to \\di"); 2471 } 2472 */ 2473 schema = null; 2474 2475 String table = null; 2476 2477 if (filter != null) { 2478 int dotat = filter.indexOf('.'); 2479 2480 schema = ((dotat > 0) ? filter.substring(0, dotat) 2481 : null); 2482 2483 if (dotat < filter.length() - 1) { 2484 2485 // Not a schema-only specifier 2486 table = ((dotat > 0) ? filter.substring(dotat + 1) 2487 : filter); 2488 } 2489 2490 filter = null; 2491 } 2492 2493 // N.b. Oracle incorrectly reports the INDEX SCHEMA as 2494 // the TABLE SCHEMA. The Metadata structure seems to 2495 // be designed with the assumption that the INDEX schema 2496 // will be the same as the TABLE schema. 2497 rs = md.getIndexInfo(null, schema, table, false, true); 2498 2499 if (rs == null) { 2500 throw new BadSpecial( 2501 "Failed to get metadata from database"); 2502 } 2503 2504 displayResultSet(null, rs, listMDIndexCols, null); 2505 2506 return; 2507 2508 default : 2509 throw new BadSpecial("Unknown describe option: '" + c 2510 + "'"); 2511 } 2512 2513 if (statement == null) { 2514 if (dbProductName.indexOf("HSQL") > -1) { 2515 listSet = listMDTableCols[HSQLDB_ELEMENT]; 2516 } else if (dbProductName.indexOf("Oracle") > -1) { 2517 listSet = listMDTableCols[ORACLE_ELEMENT]; 2518 } else { 2519 listSet = listMDTableCols[DEFAULT_ELEMENT]; 2520 } 2521 2522 if (schema == null && filter != null 2523 && filter.charAt(filter.length() - 1) == '.') { 2524 schema = filter.substring(0, filter.length() - 1); 2525 filter = null; 2526 } 2527 } 2528 2529 rs = ((statement == null) 2530 ? md.getTables(null, schema, null, types) 2531 : statement.getResultSet()); 2532 2533 if (rs == null) { 2534 throw new BadSpecial("Failed to get metadata from database"); 2535 } 2536 2537 displayResultSet(null, rs, listSet, filter); 2538 2539 if (additionalSchemas != null) { 2540 for (int i = 1; i < additionalSchemas.length; i++) { 2541 /* 2542 * Inefficient, but we have to do each successful query 2543 * twice in order to prevent calling displayResultSet 2544 * for empty/non-existent schemas 2545 */ 2546 rs = md.getTables(null, additionalSchemas[i], null, 2547 types); 2548 2549 if (rs == null) { 2550 throw new BadSpecial( 2551 "Failed to get metadata from database for '" 2552 + additionalSchemas[i] + "'"); 2553 } 2554 2555 if (!rs.next()) { 2556 continue; 2557 } 2558 2559 displayResultSet( 2560 null, 2561 md.getTables( 2562 null, additionalSchemas[i], null, types), listSet, filter); 2563 } 2564 } 2565 } catch (SQLException se) { 2566 throw new BadSpecial("Failure getting MetaData: " + se); 2567 } catch (NullPointerException npe) { 2568 throw new BadSpecial("Failure getting MetaData (NPE)"); 2569 } finally { 2570 excludeSysSchemas = false; 2571 2572 if (rs != null) { 2573 rs = null; 2574 } 2575 2576 if (statement != null) { 2577 try { 2578 statement.close(); 2579 } catch (Exception e) {} 2580 2581 statement = null; 2582 } 2583 } 2584 } 2585 2586 private boolean excludeSysSchemas = false; 2587 2588 /** 2589 * Process the current command as an SQL Statement 2590 */ 2591 private void processSQL() throws SQLException { 2592 2593 // Really don't know whether to take the network latency hit here 2594 // in order to check autoCommit in order to set 2595 // possiblyUncommitteds more accurately. 2596 // I'm going with "NO" for now, since autoCommit will usually be off. 2597 // If we do ever check autocommit, we have to keep track of the 2598 // autocommit state when every SQL statement is run, since I may 2599 // be able to have uncommitted DML, turn autocommit on, then run 2600 // other DDL with autocommit on. As a result, I could be running 2601 // SQL commands with autotommit on but still have uncommitted mods. 2602 String sql = (plMode ? dereference(curCommand, true) 2603 : curCommand); 2604 Statement statement = null; 2605 2606 if (doPrepare) { 2607 if (sql.indexOf('?') < 1) { 2608 throw new SQLException( 2609 "Prepared statements must contain one '?'"); 2610 } 2611 2612 doPrepare = false; 2613 2614 PreparedStatement ps = curConn.prepareStatement(sql); 2615 2616 if (prepareVar == null) { 2617 if (binBuffer == null) { 2618 throw new SQLException("Binary SqlFile buffer is empty"); 2619 } 2620 2621 ps.setBytes(1, binBuffer); 2622 } else { 2623 String val = (String) userVars.get(prepareVar); 2624 2625 if (val == null) { 2626 throw new SQLException("PL Variable '" + prepareVar 2627 + "' is empty"); 2628 } 2629 2630 prepareVar = null; 2631 2632 ps.setString(1, val); 2633 } 2634 2635 ps.executeUpdate(); 2636 2637 statement = ps; 2638 } else { 2639 statement = curConn.createStatement(); 2640 2641 statement.execute(sql); 2642 } 2643 2644 possiblyUncommitteds.set(true); 2645 2646 try { 2647 displayResultSet(statement, statement.getResultSet(), null, null); 2648 } finally { 2649 try { 2650 statement.close(); 2651 } catch (Exception e) {} 2652 } 2653 } 2654 2655 /** 2656 * Display the given result set for user. 2657 * The last 3 params are to narrow down records and columns where 2658 * that can not be done with a where clause (like in metadata queries). 2659 * 2660 * @param statement The SQL Statement that the result set is for. 2661 * (This is so we can get the statement's update count. 2662 * Can be null for non-update queries.) 2663 * @param r The ResultSet to display. 2664 * @param incCols Optional list of which columns to include (i.e., if 2665 * given, then other columns will be skipped). 2666 * @param incFilter Optional case-insensitive substring. 2667 * Rows are skipped which to not contain this substring. 2668 */ 2669 @SuppressWarnings({ "unused", "unchecked" }) 2670 private void displayResultSet(Statement statement, ResultSet r, 2671 int[] incCols, 2672 String filter) throws SQLException { 2673 2674 java.sql.Timestamp ts; 2675 int updateCount = (statement == null) ? -1 2676 : statement 2677 .getUpdateCount(); 2678 boolean silent = silentFetch; 2679 boolean binary = fetchBinary; 2680 2681 silentFetch = false; 2682 fetchBinary = false; 2683 2684 if (excludeSysSchemas) { 2685 stdprintln( 2686 "*** WARNING:\n*** Omitting tables from system-supplied " 2687 + "schemas\n*** (because Oracle(TM) " 2688 + "doesn't differentiate them to JDBC)."); 2689 } 2690 2691 switch (updateCount) { 2692 2693 case -1 : 2694 if (r == null) { 2695 stdprintln("No result", true); 2696 2697 break; 2698 } 2699 2700 ResultSetMetaData m = r.getMetaData(); 2701 int cols = m.getColumnCount(); 2702 int incCount = (incCols == null) ? cols 2703 : incCols 2704 .length; 2705 String val; 2706 ArrayList rows = new ArrayList(); 2707 String[] headerArray = null; 2708 String[] fieldArray; 2709 int[] maxWidth = new int[incCount]; 2710 int insi; 2711 boolean skip; 2712 @SuppressWarnings("unused") 2713 boolean ok; 2714 2715 // STEP 1: GATHER DATA 2716 if (!htmlMode) { 2717 for (int i = 0; i < maxWidth.length; i++) { 2718 maxWidth[i] = 0; 2719 } 2720 } 2721 2722 boolean[] rightJust = new boolean[incCount]; 2723 int[] dataType = new int[incCount]; 2724 boolean[] autonulls = new boolean[incCount]; 2725 2726 insi = -1; 2727 headerArray = new String[incCount]; 2728 2729 for (int i = 1; i <= cols; i++) { 2730 if (incCols != null) { 2731 skip = true; 2732 2733 for (int j = 0; j < incCols.length; j++) { 2734 if (i == incCols[j]) { 2735 skip = false; 2736 } 2737 } 2738 2739 if (skip) { 2740 continue; 2741 } 2742 } 2743 2744 headerArray[++insi] = m.getColumnLabel(i); 2745 dataType[insi] = m.getColumnType(i); 2746 rightJust[insi] = false; 2747 autonulls[insi] = true; 2748 2749 switch (dataType[insi]) { 2750 2751 case java.sql.Types.BIGINT : 2752 case java.sql.Types.BIT : 2753 case java.sql.Types.DECIMAL : 2754 case java.sql.Types.DOUBLE : 2755 case java.sql.Types.FLOAT : 2756 case java.sql.Types.INTEGER : 2757 case java.sql.Types.NUMERIC : 2758 case java.sql.Types.REAL : 2759 case java.sql.Types.SMALLINT : 2760 case java.sql.Types.TINYINT : 2761 rightJust[insi] = true; 2762 break; 2763 2764 case java.sql.Types.VARBINARY : 2765 case java.sql.Types.VARCHAR : 2766 autonulls[insi] = false; 2767 break; 2768 } 2769 2770 if (htmlMode) { 2771 continue; 2772 } 2773 2774 if (headerArray[insi].length() > maxWidth[insi]) { 2775 maxWidth[insi] = headerArray[insi].length(); 2776 } 2777 } 2778 2779 boolean filteredOut; 2780 2781 EACH_ROW: 2782 while (r.next()) { 2783 fieldArray = new String[incCount]; 2784 insi = -1; 2785 filteredOut = filter != null; 2786 2787 for (int i = 1; i <= cols; i++) { 2788 2789 // This is the only case where we can save a data 2790 // read by recognizing we don't need this datum early. 2791 if (incCols != null) { 2792 skip = true; 2793 2794 for (int j = 0; j < incCols.length; j++) { 2795 if (i == incCols[j]) { 2796 skip = false; 2797 } 2798 } 2799 2800 if (skip) { 2801 continue; 2802 } 2803 } 2804 2805 // This row may still be ditched, but it is now 2806 // certain that we need to increment the fieldArray 2807 // index. 2808 ++insi; 2809 2810 if (!canDisplayType(dataType[insi])) { 2811 binary = true; 2812 } 2813 2814 val = null; 2815 2816 if (!binary) { 2817 2818 // The special formatting for Timestamps is 2819 // because the most popular current databases 2820 // are VERY inconsistent about the format 2821 // returned by getString() for a Timestamp field. 2822 // In many cases, the output is very user- 2823 // unfriendly. However, getTimestamp().toString() 2824 // is consistent and convenient. 2825 if (dataType[insi] == java.sql.Types.TIMESTAMP) { 2826 ts = r.getTimestamp(i); 2827 val = ((ts == null) ? null 2828 : ts.toString()); 2829 } else { 2830 val = r.getString(i); 2831 2832 // If we tried to get a String but it failed, 2833 // try getting it with a String Stream 2834 if (val == null) { 2835 try { 2836 val = streamToString( 2837 r.getAsciiStream(i)); 2838 } catch (Exception e) {} 2839 } 2840 } 2841 } 2842 2843 if (binary || (val == null &&!r.wasNull())) { 2844 if (pwCsv != null) { 2845 2846 // TODO: Should throw something other than 2847 // a SQLException 2848 throw new SQLException( 2849 "Table has a binary column. CSV files " 2850 + "are text, not binary, files"); 2851 } 2852 2853 // DB has a value but we either explicitly want 2854 // it as binary, or we failed to get it as String. 2855 try { 2856 binBuffer = 2857 streamToBytes(r.getBinaryStream(i)); 2858 } catch (IOException ioe) { 2859 throw new SQLException( 2860 "Failed to read value using stream"); 2861 } 2862 2863 stdprintln("Read " + binBuffer.length 2864 + " bytes from field '" 2865 + headerArray[insi] + "' (type " 2866 + sqlTypeToString(dataType[insi]) 2867 + ") into binary buffer"); 2868 2869 return; 2870 } 2871 2872 if (excludeSysSchemas && i == 2) { 2873 for (int z = 0; z < oracleSysSchemas.length; 2874 z++) { 2875 if (val.equals(oracleSysSchemas[z])) { 2876 filteredOut = true; 2877 2878 break; 2879 } 2880 } 2881 } 2882 2883 if (fetchingVar != null) { 2884 userVars.put(fetchingVar, val); 2885 2886 fetchingVar = null; 2887 } 2888 2889 if (silent) { 2890 return; 2891 } 2892 2893 // We do not omit rows here. We collect information 2894 // so we can make the decision after all rows are 2895 // read in. 2896 if (filter != null 2897 && (val == null 2898 || val.indexOf(filter) > -1)) { 2899 filteredOut = false; 2900 } 2901 2902 /////////////////////////////// 2903 // A little tricky here. fieldArray[] MUST get set. 2904 if (val == null && pwCsv == null) { 2905 if (dataType[insi] == java.sql.Types.VARCHAR) { 2906 fieldArray[insi] = (htmlMode ? "<I>null</I>" 2907 : "[null]"); 2908 } else { 2909 fieldArray[insi] = ""; 2910 } 2911 } else { 2912 fieldArray[insi] = val; 2913 } 2914 2915 /////////////////////////////// 2916 if (htmlMode || pwCsv != null) { 2917 continue; 2918 } 2919 2920 if (fieldArray[insi].length() > maxWidth[insi]) { 2921 maxWidth[insi] = fieldArray[insi].length(); 2922 } 2923 } 2924 2925 if (!filteredOut) { 2926 rows.add(fieldArray); 2927 } 2928 } 2929 2930 // STEP 2: DISPLAY DATA (= 2a OR 2b) 2931 // STEP 2a (Non-CSV) 2932 if (pwCsv == null) { 2933 condlPrintln("<TABLE border='1'>", true); 2934 2935 if (incCount > 1) { 2936 condlPrint(htmlRow(COL_HEAD) + '\n' + PRE_TD, true); 2937 2938 for (int i = 0; i < headerArray.length; i++) { 2939 condlPrint("<TD>" + headerArray[i] + "</TD>", 2940 true); 2941 condlPrint(((i > 0) ? spaces(2) 2942 : "") + pad( 2943 headerArray[i], 2944 maxWidth[i], 2945 rightJust[i], 2946 (i < headerArray.length 2947 - 1 || rightJust[i])), false); 2948 } 2949 2950 condlPrintln("\n" + PRE_TR + "</TR>", true); 2951 condlPrintln("", false); 2952 2953 if (!htmlMode) { 2954 for (int i = 0; i < headerArray.length; i++) { 2955 condlPrint(((i > 0) ? spaces(2) 2956 : "") + divider( 2957 maxWidth[i]), false); 2958 } 2959 2960 condlPrintln("", false); 2961 } 2962 } 2963 2964 for (int i = 0; i < rows.size(); i++) { 2965 condlPrint(htmlRow(((i % 2) == 0) ? COL_EVEN 2966 : COL_ODD) + '\n' 2967 + PRE_TD, true); 2968 2969 fieldArray = (String[]) rows.get(i); 2970 2971 for (int j = 0; j < fieldArray.length; j++) { 2972 condlPrint("<TD>" + fieldArray[j] + "</TD>", 2973 true); 2974 condlPrint(((j > 0) ? spaces(2) 2975 : "") + pad( 2976 fieldArray[j], 2977 maxWidth[j], 2978 rightJust[j], 2979 (j < fieldArray.length 2980 - 1 || rightJust[j])), false); 2981 } 2982 2983 condlPrintln("\n" + PRE_TR + "</TR>", true); 2984 condlPrintln("", false); 2985 } 2986 2987 condlPrintln("</TABLE>", true); 2988 2989 if (rows.size() != 1) { 2990 stdprintln("\n" + rows.size() + " rows", true); 2991 } 2992 2993 condlPrintln("<HR>", true); 2994 2995 break; 2996 } 2997 2998 // STEP 2b (CSV) 2999 if (incCount > 0) { 3000 for (int i = 0; i < headerArray.length; i++) { 3001 csvSafe(headerArray[i]); 3002 pwCsv.print(headerArray[i]); 3003 3004 if (i < headerArray.length - 1) { 3005 pwCsv.print(csvColDelim); 3006 } 3007 } 3008 3009 pwCsv.print(csvRowDelim); 3010 } 3011 3012 for (int i = 0; i < rows.size(); i++) { 3013 fieldArray = (String[]) rows.get(i); 3014 3015 for (int j = 0; j < fieldArray.length; j++) { 3016 csvSafe(fieldArray[j]); 3017 pwCsv.print((fieldArray[j] == null) 3018 ? (autonulls[j] ? "" 3019 : csvNullRep) 3020 : fieldArray[j]); 3021 3022 if (j < fieldArray.length - 1) { 3023 pwCsv.print(csvColDelim); 3024 } 3025 } 3026 3027 pwCsv.print(csvRowDelim); 3028 } 3029 3030 stdprintln(Integer.toString(rows.size()) 3031 + " rows read from DB"); 3032 break; 3033 3034 default : 3035 if (fetchingVar != null) { 3036 userVars.put(fetchingVar, Integer.toString(updateCount)); 3037 3038 fetchingVar = null; 3039 } 3040 3041 if (updateCount != 0) { 3042 stdprintln(Integer.toString(updateCount) + " row" 3043 + ((updateCount == 1) ? "" 3044 : "s") + " updated"); 3045 } 3046 break; 3047 } 3048 } 3049 3050 private static final int COL_HEAD = 0, 3051 COL_ODD = 1, 3052 COL_EVEN = 2 3053 ; 3054 private static final String PRE_TR = spaces(4); 3055 private static final String PRE_TD = spaces(8); 3056 3057 /** 3058 * Print a properly formatted HTML <TR> command for the given 3059 * situation. 3060 * 3061 * @param colType Column type: COL_HEAD, COL_ODD or COL_EVEN. 3062 */ 3063 private static String htmlRow(int colType) { 3064 3065 switch (colType) { 3066 3067 case COL_HEAD : 3068 return PRE_TR + "<TR style='font-weight: bold;'>"; 3069 3070 case COL_ODD : 3071 return PRE_TR 3072 + "<TR style='background: #94d6ef; font: normal " 3073 + "normal 10px/10px Arial, Helvitica, sans-serif;'>"; 3074 3075 case COL_EVEN : 3076 return PRE_TR 3077 + "<TR style='background: silver; font: normal " 3078 + "normal 10px/10px Arial, Helvitica, sans-serif;'>"; 3079 } 3080 3081 return null; 3082 } 3083 3084 /** 3085 * Returns a divider of hypens of requested length. 3086 * 3087 * @param len Length of output String. 3088 */ 3089 private static String divider(int len) { 3090 return (len > DIVIDER.length()) ? DIVIDER 3091 : DIVIDER.substring(0, len); 3092 } 3093 3094 /** 3095 * Returns a String of spaces of requested length. 3096 * 3097 * @param len Length of output String. 3098 */ 3099 private static String spaces(int len) { 3100 return (len > SPACES.length()) ? SPACES 3101 : SPACES.substring(0, len); 3102 } 3103 3104 /** 3105 * Pads given input string out to requested length with space 3106 * characters. 3107 * 3108 * @param inString Base string. 3109 * @param fulllen Output String length. 3110 * @param rightJustify True to right justify, false to left justify. 3111 */ 3112 private static String pad(String inString, int fulllen, 3113 boolean rightJustify, boolean doPad) { 3114 3115 if (!doPad) { 3116 return inString; 3117 } 3118 3119 int len = fulllen - inString.length(); 3120 3121 if (len < 1) { 3122 return inString; 3123 } 3124 3125 String pad = spaces(len); 3126 3127 return ((rightJustify ? pad 3128 : "") + inString + (rightJustify ? "" 3129 : pad)); 3130 } 3131 3132 /** 3133 * Display command history, which consists of complete or incomplete SQL 3134 * commands. 3135 */ 3136 private void showHistory() { 3137 3138 int ctr = -1; 3139 String s; 3140 String[] reversedList = new String[statementHistory.length]; 3141 3142 try { 3143 for (int i = curHist; i >= 0; i--) { 3144 s = statementHistory[i]; 3145 3146 if (s == null) { 3147 return; 3148 } 3149 3150 reversedList[++ctr] = s; 3151 } 3152 3153 for (int i = statementHistory.length - 1; i > curHist; i--) { 3154 s = statementHistory[i]; 3155 3156 if (s == null) { 3157 return; 3158 } 3159 3160 reversedList[++ctr] = s; 3161 } 3162 } finally { 3163 if (ctr < 0) { 3164 stdprintln("<<< No history yet >>>"); 3165 3166 return; 3167 } 3168 3169 for (int i = ctr; i >= 0; i--) { 3170 psStd.println(((i == 0) ? "BUFR" 3171 : ("-" + i + " ")) + " **********************************************\n" 3172 + reversedList[i]); 3173 } 3174 3175 psStd.println( 3176 "\n<<< Copy a command to buffer like \"\\-3\" " 3177 + "Re-execute buffer like \":;\" >>>"); 3178 } 3179 } 3180 3181 /** 3182 * Return a SQL Command from command history. 3183 */ 3184 private String commandFromHistory(int commandsAgo) throws BadSpecial { 3185 3186 if (commandsAgo >= statementHistory.length) { 3187 throw new BadSpecial("History can only hold up to " 3188 + statementHistory.length + " commands"); 3189 } 3190 3191 String s = 3192 statementHistory[(statementHistory.length + curHist - commandsAgo) % statementHistory.length]; 3193 3194 if (s == null) { 3195 throw new BadSpecial("History doesn't go back that far"); 3196 } 3197 3198 return s; 3199 } 3200 3201 /** 3202 * Push a command onto the history array (the first element of which 3203 * is the "Buffer"). 3204 */ 3205 private void setBuf(String inString) { 3206 3207 curHist++; 3208 3209 if (curHist == statementHistory.length) { 3210 curHist = 0; 3211 } 3212 3213 statementHistory[curHist] = inString; 3214 } 3215 3216 /** 3217 * Describe the columns of specified table. 3218 * 3219 * @param tableName Table that will be described. 3220 * @param filter Substring to filter by 3221 */ 3222 @SuppressWarnings({ "unused", "unchecked" }) 3223 private void describe(String tableName, 3224 String inFilter) throws SQLException { 3225 3226 /* 3227 * Doing case-sensitive filters now, for greater portability. 3228 String filter = ((inFilter == null) ? null : inFilter.toUpperCase()); 3229 */ 3230 String filter = inFilter; 3231 3232 String val; 3233 ArrayList rows = new ArrayList(); 3234 String[] headerArray = { 3235 "name", "datatype", "width", "no-nulls" 3236 }; 3237 String[] fieldArray; 3238 int[] maxWidth = { 3239 0, 0, 0, 0 3240 }; 3241 boolean[] rightJust = { 3242 false, false, true, false 3243 }; 3244 3245 // STEP 1: GATHER DATA 3246 for (int i = 0; i < headerArray.length; i++) { 3247 if (htmlMode) { 3248 continue; 3249 } 3250 3251 if (headerArray[i].length() > maxWidth[i]) { 3252 maxWidth[i] = headerArray[i].length(); 3253 } 3254 } 3255 3256 Statement statement = curConn.createStatement(); 3257 ResultSet r = null; 3258 3259 try { 3260 statement.execute("SELECT * FROM " + tableName + " WHERE 1 = 2"); 3261 3262 r = statement.getResultSet(); 3263 3264 ResultSetMetaData m = r.getMetaData(); 3265 int cols = m.getColumnCount(); 3266 3267 for (int i = 0; i < cols; i++) { 3268 fieldArray = new String[4]; 3269 fieldArray[0] = m.getColumnName(i + 1); 3270 3271 if (filter != null && fieldArray[0].indexOf(filter) < 0) { 3272 continue; 3273 } 3274 3275 fieldArray[1] = m.getColumnTypeName(i + 1); 3276 fieldArray[2] = Integer.toString(m.getColumnDisplaySize(i 3277 + 1)); 3278 fieldArray[3] = 3279 ((m.isNullable(i + 1) == java.sql.ResultSetMetaData.columnNullable) 3280 ? (htmlMode ? " " 3281 : "") 3282 : "*"); 3283 3284 rows.add(fieldArray); 3285 3286 for (int j = 0; j < fieldArray.length; j++) { 3287 if (fieldArray[j].length() > maxWidth[j]) { 3288 maxWidth[j] = fieldArray[j].length(); 3289 } 3290 } 3291 } 3292 3293 // STEP 2: DISPLAY DATA 3294 condlPrint("<TABLE border='1'>\n" + htmlRow(COL_HEAD) + '\n' 3295 + PRE_TD, true); 3296 3297 for (int i = 0; i < headerArray.length; i++) { 3298 condlPrint("<TD>" + headerArray[i] + "</TD>", true); 3299 condlPrint(((i > 0) ? spaces(2) 3300 : "") + pad(headerArray[i], maxWidth[i], 3301 rightJust[i], 3302 (i < headerArray.length - 1 3303 || rightJust[i])), false); 3304 } 3305 3306 condlPrintln("\n" + PRE_TR + "</TR>", true); 3307 condlPrintln("", false); 3308 3309 if (!htmlMode) { 3310 for (int i = 0; i < headerArray.length; i++) { 3311 condlPrint(((i > 0) ? spaces(2) 3312 : "") + divider(maxWidth[i]), false); 3313 } 3314 3315 condlPrintln("", false); 3316 } 3317 3318 for (int i = 0; i < rows.size(); i++) { 3319 condlPrint(htmlRow(((i % 2) == 0) ? COL_EVEN 3320 : COL_ODD) + '\n' 3321 + PRE_TD, true); 3322 3323 fieldArray = (String[]) rows.get(i); 3324 3325 for (int j = 0; j < fieldArray.length; j++) { 3326 condlPrint("<TD>" + fieldArray[j] + "</TD>", true); 3327 condlPrint(((j > 0) ? spaces(2) 3328 : "") + pad( 3329 fieldArray[j], maxWidth[j], 3330 rightJust[j], 3331 (j < fieldArray.length - 1 3332 || rightJust[j])), false); 3333 } 3334 3335 condlPrintln("\n" + PRE_TR + "</TR>", true); 3336 condlPrintln("", false); 3337 } 3338 3339 condlPrintln("\n</TABLE>\n<HR>", true); 3340 } finally { 3341 try { 3342 if (r != null) { 3343 r.close(); 3344 3345 r = null; 3346 } 3347 3348 statement.close(); 3349 } catch (Exception e) {} 3350 } 3351 } 3352 3353 public static String[] getTokenArray(String inString) { 3354 3355 // I forget how to code a String array literal outside of a 3356 // definition. 3357 String[] mtString = {}; 3358 3359 if (inString == null) { 3360 return mtString; 3361 } 3362 3363 StringTokenizer toker = new StringTokenizer(inString); 3364 String[] sa = new String[toker.countTokens()]; 3365 3366 for (int i = 0; i < sa.length; i++) { 3367 sa[i] = toker.nextToken(); 3368 } 3369 3370 return sa; 3371 } 3372 3373 private boolean eval(String[] inTokens) throws BadSpecial { 3374 3375 // dereference *VARNAME variables. 3376 // N.b. we work with a "copy" of the tokens. 3377 boolean negate = inTokens.length > 0 && inTokens[0].equals("!"); 3378 String[] tokens = new String[negate ? (inTokens.length - 1) 3379 : inTokens.length]; 3380 3381 for (int i = 0; i < tokens.length; i++) { 3382 tokens[i] = (inTokens[i + (negate ? 1 3383 : 0)].length() > 1 && inTokens[i + (negate ? 1 3384 : 0)].charAt( 3385 0) == '*') ? ((String) userVars.get( 3386 inTokens[i + (negate ? 1 3387 : 0)] 3388 .substring( 3389 1))) 3390 : inTokens[i + (negate ? 1 3391 : 0)]; 3392 3393 if (tokens[i] == null) { 3394 tokens[i] = ""; 3395 } 3396 } 3397 3398 if (tokens.length == 1) { 3399 return (tokens[0].length() > 0 &&!tokens[0].equals("0")) ^ negate; 3400 } 3401 3402 if (tokens.length == 3) { 3403 if (tokens[1].equals("==")) { 3404 return tokens[0].equals(tokens[2]) ^ negate; 3405 } 3406 3407 if (tokens[1].equals("!=") || tokens[1].equals("<>") 3408 || tokens[1].equals("><")) { 3409 return (!tokens[0].equals(tokens[2])) ^ negate; 3410 } 3411 3412 if (tokens[1].equals(">")) { 3413 return (tokens[0].length() > tokens[2].length() || ((tokens[0].length() == tokens[2].length()) && tokens[0].compareTo(tokens[2]) > 0)) 3414 ^ negate; 3415 } 3416 3417 if (tokens[1].equals("<")) { 3418 return (tokens[2].length() > tokens[0].length() || ((tokens[2].length() == tokens[0].length()) && tokens[2].compareTo(tokens[0]) > 0)) 3419 ^ negate; 3420 } 3421 } 3422 3423 throw new BadSpecial("Unrecognized logical operation"); 3424 } 3425 3426 private void closeQueryOutputStream() { 3427 3428 if (pwQuery == null) { 3429 return; 3430 } 3431 3432 if (htmlMode) { 3433 pwQuery.println("</BODY></HTML>"); 3434 pwQuery.flush(); 3435 } 3436 3437 pwQuery.close(); 3438 3439 pwQuery = null; 3440 } 3441 3442 /** 3443 * Print to psStd and possibly pwQuery iff current HTML mode matches 3444 * supplied printHtml. 3445 */ 3446 private void condlPrintln(String s, boolean printHtml) { 3447 3448 if ((printHtml &&!htmlMode) || (htmlMode &&!printHtml)) { 3449 return; 3450 } 3451 3452 psStd.println(s); 3453 3454 if (pwQuery != null) { 3455 pwQuery.println(s); 3456 pwQuery.flush(); 3457 } 3458 } 3459 3460 /** 3461 * Print to psStd and possibly pwQuery iff current HTML mode matches 3462 * supplied printHtml. 3463 */ 3464 private void condlPrint(String s, boolean printHtml) { 3465 3466 if ((printHtml &&!htmlMode) || (htmlMode &&!printHtml)) { 3467 return; 3468 } 3469 3470 psStd.print(s); 3471 3472 if (pwQuery != null) { 3473 pwQuery.print(s); 3474 pwQuery.flush(); 3475 } 3476 } 3477 3478 3479 private static String formatNicely(Map map, boolean withValues) { 3480 3481 String key; 3482 StringBuffer sb = new StringBuffer(); 3483 Iterator it = (new TreeMap(map)).keySet().iterator(); 3484 3485 if (withValues) { 3486 sb.append("The outermost parentheses are not part of " 3487 + "the values.\n"); 3488 } else { 3489 sb.append("Showing variable names and length of values " 3490 + "(use 'listvalue' to see values).\n"); 3491 } 3492 3493 while (it.hasNext()) { 3494 key = (String) it.next(); 3495 3496 String s = (String) map.get(key); 3497 3498 sb.append(" " + key + ": " + (withValues ? ("(" + s + ')') 3499 : Integer.toString( 3500 s.length())) + '\n'); 3501 } 3502 3503 return sb.toString(); 3504 } 3505 3506 /** 3507 * Ascii file dump. 3508 */ 3509 private void dump(String varName, 3510 File dumpFile) throws IOException, BadSpecial { 3511 3512 String val = (String) userVars.get(varName); 3513 3514 if (val == null) { 3515 throw new BadSpecial("Variable '" + varName 3516 + "' has no value set"); 3517 } 3518 3519 OutputStreamWriter osw = 3520 new OutputStreamWriter(new FileOutputStream(dumpFile), charset); 3521 3522 osw.write(val); 3523 3524 boolean terminated = false; 3525 3526 if (val.length() > 0) { 3527 char lastChar = val.charAt(val.length() - 1); 3528 3529 if (lastChar != '\n' && lastChar != '\r') { 3530 terminated = true; 3531 3532 osw.write('\n'); // I hope this really writes \r\n for DOS 3533 } 3534 } 3535 3536 osw.flush(); 3537 osw.close(); 3538 3539 // Since opened in overwrite mode, since we didn't exception out, 3540 // we can be confident that we wrote all the bytest in the file. 3541 stdprintln("Saved " + dumpFile.length() + " characters to '" 3542 + dumpFile + "'"); 3543 } 3544 3545 byte[] binBuffer = null; 3546 3547 /** 3548 * Binary file dump 3549 */ 3550 private void dump(File dumpFile) throws IOException, BadSpecial { 3551 3552 if (binBuffer == null) { 3553 throw new BadSpecial("Binary SqlFile buffer is currently empty"); 3554 } 3555 3556 FileOutputStream fos = new FileOutputStream(dumpFile); 3557 3558 fos.write(binBuffer); 3559 3560 int len = binBuffer.length; 3561 3562 binBuffer = null; 3563 3564 fos.flush(); 3565 fos.close(); 3566 stdprintln("Saved " + len + " bytes to '" + dumpFile + "'"); 3567 } 3568 3569 private String streamToString(InputStream is) throws IOException { 3570 3571 char[] xferBuffer = new char[10240]; 3572 StringWriter stringWriter = new StringWriter(); 3573 InputStreamReader isr = new InputStreamReader(is, charset); 3574 int i; 3575 3576 while ((i = isr.read(xferBuffer)) > 0) { 3577 stringWriter.write(xferBuffer, 0, i); 3578 } 3579 3580 return stringWriter.toString(); 3581 } 3582 3583 private byte[] streamToBytes(InputStream is) throws IOException { 3584 3585 byte[] xferBuffer = new byte[10240]; 3586 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 3587 int i; 3588 3589 while ((i = is.read(xferBuffer)) > 0) { 3590 baos.write(xferBuffer, 0, i); 3591 } 3592 3593 return baos.toByteArray(); 3594 } 3595 3596 /** 3597 * Ascii file load. 3598 */ 3599 private void load(String varName, File asciiFile) throws IOException { 3600 3601 char[] xferBuffer = new char[10240]; 3602 StringWriter stringWriter = new StringWriter(); 3603 InputStreamReader isr = 3604 new InputStreamReader(new FileInputStream(asciiFile), charset); 3605 int i; 3606 3607 while ((i = isr.read(xferBuffer)) > 0) { 3608 stringWriter.write(xferBuffer, 0, i); 3609 } 3610 3611 isr.close(); 3612 userVars.put(varName, stringWriter.toString()); 3613 } 3614 3615 /** 3616 * Binary file load 3617 */ 3618 private void load(File binFile) throws IOException { 3619 3620 byte[] xferBuffer = new byte[10240]; 3621 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 3622 FileInputStream fis = new FileInputStream(binFile); 3623 int i; 3624 3625 while ((i = fis.read(xferBuffer)) > 0) { 3626 baos.write(xferBuffer, 0, i); 3627 } 3628 3629 fis.close(); 3630 3631 binBuffer = baos.toByteArray(); 3632 3633 stdprintln("Loaded " + binBuffer.length 3634 + " bytes into Binary buffer"); 3635 } 3636 3637 /** 3638 * This method is used to tell SqlFile whether this Sql Type must 3639 * ALWAYS be loaded to the binary buffer without displaying. 3640 * 3641 * N.b.: If this returns "true" for a type, then the user can never 3642 * "see" values for these columns. 3643 * Therefore, if a type may-or-may-not-be displayable, better to return 3644 * false here and let the user choose. 3645 * In general, if there is a toString() operator for this Sql Type 3646 * then return false, since the JDBC driver should know how to make the 3647 * value displayable. 3648 * 3649 * The table on this page lists the most common SqlTypes, all of which 3650 * must implement toString(): 3651 * http://java.sun.com/docs/books/tutorial/jdbc/basics/retrieving.html 3652 * 3653 * @see java.sql.Types 3654 */ 3655 public static boolean canDisplayType(int i) { 3656 3657 /* I don't now about some of the more obscure types, like REF and 3658 * DATALINK */ 3659 switch (i) { 3660 3661 //case java.sql.Types.BINARY : 3662 case java.sql.Types.BLOB : 3663 case java.sql.Types.JAVA_OBJECT : 3664 3665 //case java.sql.Types.LONGVARBINARY : 3666 //case java.sql.Types.LONGVARCHAR : 3667 case java.sql.Types.OTHER : 3668 case java.sql.Types.STRUCT : 3669 3670 //case java.sql.Types.VARBINARY : 3671 return false; 3672 } 3673 3674 return true; 3675 } 3676 3677 // won't compile with JDK 1.3 without these 3678 private static final int JDBC3_BOOLEAN = 16; 3679 private static final int JDBC3_DATALINK = 70; 3680 3681 public static String sqlTypeToString(int i) { 3682 3683 switch (i) { 3684 3685 case java.sql.Types.ARRAY : 3686 return "ARRAY"; 3687 3688 case java.sql.Types.BIGINT : 3689 return "BIGINT"; 3690 3691 case java.sql.Types.BINARY : 3692 return "BINARY"; 3693 3694 case java.sql.Types.BIT : 3695 return "BIT"; 3696 3697 case java.sql.Types.BLOB : 3698 return "BLOB"; 3699 3700 case JDBC3_BOOLEAN : 3701 return "BOOLEAN"; 3702 3703 case java.sql.Types.CHAR : 3704 return "CHAR"; 3705 3706 case java.sql.Types.CLOB : 3707 return "CLOB"; 3708 3709 case JDBC3_DATALINK : 3710 return "DATALINK"; 3711 3712 case java.sql.Types.DATE : 3713 return "DATE"; 3714 3715 case java.sql.Types.DECIMAL : 3716 return "DECIMAL"; 3717 3718 case java.sql.Types.DISTINCT : 3719 return "DISTINCT"; 3720 3721 case java.sql.Types.DOUBLE : 3722 return "DOUBLE"; 3723 3724 case java.sql.Types.FLOAT : 3725 return "FLOAT"; 3726 3727 case java.sql.Types.INTEGER : 3728 return "INTEGER"; 3729 3730 case java.sql.Types.JAVA_OBJECT : 3731 return "JAVA_OBJECT"; 3732 3733 case java.sql.Types.LONGVARBINARY : 3734 return "LONGVARBINARY"; 3735 3736 case java.sql.Types.LONGVARCHAR : 3737 return "LONGVARCHAR"; 3738 3739 case java.sql.Types.NULL : 3740 return "NULL"; 3741 3742 case java.sql.Types.NUMERIC : 3743 return "NUMERIC"; 3744 3745 case java.sql.Types.OTHER : 3746 return "OTHER"; 3747 3748 case java.sql.Types.REAL : 3749 return "REAL"; 3750 3751 case java.sql.Types.REF : 3752 return "REF"; 3753 3754 case java.sql.Types.SMALLINT : 3755 return "SMALLINT"; 3756 3757 case java.sql.Types.STRUCT : 3758 return "STRUCT"; 3759 3760 case java.sql.Types.TIME : 3761 return "TIME"; 3762 3763 case java.sql.Types.TIMESTAMP : 3764 return "TIMESTAMP"; 3765 3766 case java.sql.Types.TINYINT : 3767 return "TINYINT"; 3768 3769 case java.sql.Types.VARBINARY : 3770 return "VARBINARY"; 3771 3772 case java.sql.Types.VARCHAR : 3773 return "VARCHAR"; 3774 } 3775 3776 return "Unknown type " + i; 3777 } 3778 3779 /** 3780 * Validate that String is safe to display in a CSV file. 3781 * 3782 * @throws SQLException (should throw something else, since this is 3783 * not an SQL problem. Fix the caller!) 3784 */ 3785 public void csvSafe(String s) throws SQLException { 3786 3787 if (pwCsv == null || csvColDelim == null || csvRowDelim == null 3788 || csvNullRep == null) { 3789 throw new RuntimeException( 3790 "Assertion failed. \n" 3791 + "csvSafe called when CSV settings are incomplete"); 3792 } 3793 3794 if (s == null) { 3795 return; 3796 } 3797 3798 if (s.indexOf(csvColDelim) > 0) { 3799 throw new SQLException( 3800 "Table data contains our column delimiter '" + csvColDelim 3801 + "'"); 3802 } 3803 3804 if (s.indexOf(csvRowDelim) > 0) { 3805 throw new SQLException("Table data contains our row delimiter '" 3806 + csvRowDelim + "'"); 3807 } 3808 3809 if (s.indexOf(csvNullRep) > 0) { 3810 throw new SQLException( 3811 "Table data contains our null representation '" + csvNullRep 3812 + "'"); 3813 } 3814 } 3815 3816 public static String convertEscapes(String inString) { 3817 3818 if (inString == null) { 3819 return null; 3820 } 3821 3822 String workString = new String(inString); 3823 int i; 3824 3825 i = 0; 3826 3827 while ((i = workString.indexOf("\\n", i)) > -1 3828 && i < workString.length() - 1) { 3829 workString = workString.substring(0, i) + '\n' 3830 + workString.substring(i + 2); 3831 } 3832 3833 i = 0; 3834 3835 while ((i = workString.indexOf("\\r", i)) > -1 3836 && i < workString.length() - 1) { 3837 workString = workString.substring(0, i) + '\r' 3838 + workString.substring(i + 2); 3839 } 3840 3841 i = 0; 3842 3843 while ((i = workString.indexOf("\\t", i)) > -1 3844 && i < workString.length() - 1) { 3845 workString = workString.substring(0, i) + '\t' 3846 + workString.substring(i + 2); 3847 } 3848 3849 return workString; 3850 } 3851 3852 /** 3853 * Name is self-explanatory. 3854 * 3855 * If there is user demand, open file in random access mode so don't 3856 * need to load 2 copies of the entire file into memory. 3857 * This will be difficult because can't use standard Java language 3858 * features to search through a character array for multi-character 3859 * substrings. 3860 */ 3861 public void importCsv(String filePath) throws IOException, BadSpecial { 3862 3863 char[] bfr = null; 3864 File file = new File(filePath); 3865 3866 if (!file.canRead()) { 3867 throw new IOException("Can't read file '" + file + "'"); 3868 } 3869 3870 int fileLength = (int) (file.length()); 3871 3872 try { 3873 bfr = new char[fileLength]; 3874 } catch (RuntimeException re) { 3875 throw new IOException( 3876 "SqlFile can only read in your CSV file in one chunk at this time.\n" 3877 + "Please run the program with more RAM (try Java -Xm* switches)."); 3878 } 3879 3880 InputStreamReader isr = 3881 new InputStreamReader(new FileInputStream(file), charset); 3882 int retval = isr.read(bfr, 0, bfr.length); 3883 3884 isr.close(); 3885 3886 if (retval != bfr.length) { 3887 throw new IOException("Didn't read all characters. Read in " 3888 + retval + " characters"); 3889 } 3890 3891 String string = null; 3892 3893 try { 3894 string = new String(bfr); 3895 } catch (RuntimeException re) { 3896 throw new IOException( 3897 "SqlFile converts your entire CSV file to a String at this time.\n" 3898 + "Please run the program with more RAM (try Java -Xm* switches)."); 3899 } 3900 3901 ArrayList headerList = new ArrayList(); 3902 String recordString; 3903 3904 // N.b. ENDs are the index of 1 PAST the current item 3905 int recEnd; 3906 int colStart; 3907 int colEnd; 3908 3909 // First read header line 3910 int recStart = 0; 3911 3912 recEnd = string.indexOf(csvRowDelim, recStart); 3913 3914 if (recEnd < 0) { 3915 3916 // File consists of only a header line 3917 recEnd = string.length(); 3918 } 3919 3920 colStart = recStart; 3921 colEnd = -1; 3922 3923 while (true) { 3924 if (colEnd == recEnd) { 3925 3926 // We processed final column last time through loop 3927 break; 3928 } 3929 3930 colEnd = string.indexOf(csvColDelim, colStart); 3931 3932 if (colEnd < 0 || colEnd > recEnd) { 3933 colEnd = recEnd; 3934 } 3935 3936 if (colEnd - colStart < 1) { 3937 throw new IOException("No column header for column " 3938 + (headerList.size() + 1)); 3939 } 3940 3941 headerList.add(string.substring(colStart, colEnd)); 3942 3943 colStart = colEnd + csvColDelim.length(); 3944 } 3945 3946 String[] headers = (String[]) headerList.toArray(new String[0]); 3947 boolean[] autonulls = new boolean[headers.length]; 3948 String tableName = (String) userVars.get("*CSV_TABLENAME"); 3949 3950 if (tableName == null) { 3951 tableName = file.getName(); 3952 3953 int i = tableName.lastIndexOf('.'); 3954 3955 if (i > 0) { 3956 tableName = tableName.substring(0, i); 3957 } 3958 } 3959 3960 StringBuffer tmpSb = new StringBuffer(); 3961 3962 for (int i = 0; i < headers.length; i++) { 3963 if (i > 0) { 3964 tmpSb.append(", "); 3965 } 3966 3967 tmpSb.append(headers[i]); 3968 } 3969 3970 StringBuffer sb = new StringBuffer("INSERT INTO " + tableName + " (" 3971 + tmpSb + ") VALUES ("); 3972 StringBuffer typeQuerySb = new StringBuffer("SELECT " + tmpSb 3973 + " FROM " + tableName + " WHERE 1 = 2"); 3974 3975 try { 3976 int ctype; 3977 ResultSetMetaData rsmd = curConn.createStatement().executeQuery( 3978 typeQuerySb.toString()).getMetaData(); 3979 3980 if (rsmd.getColumnCount() != autonulls.length) { 3981 throw new BadSpecial("Metadata mismatch for columns"); 3982 } 3983 3984 for (int i = 0; i < autonulls.length; i++) { 3985 ctype = rsmd.getColumnType(i + 1); 3986 3987 // I.e., for VAR* column types, "" in CSV file means 3988 // to insert "". Otherwise, we'll insert null for "". 3989 autonulls[i] = (ctype != java.sql.Types.VARBINARY 3990 && ctype != java.sql.Types.VARCHAR); 3991 } 3992 } catch (SQLException se) { 3993 throw new BadSpecial("Failed to get metadata for query: " 3994 + se.getMessage()); 3995 } 3996 3997 for (int i = 0; i < headers.length; i++) { 3998 if (i > 0) { 3999 sb.append(", "); 4000 } 4001 4002 sb.append('?'); 4003 } 4004 4005 //System.out.println("INSERTION: (" + sb + ')'); 4006 try { 4007 PreparedStatement ps = curConn.prepareStatement(sb.toString() 4008 + ')'); 4009 String[] dataVals = new String[headers.length]; 4010 int recCount = 0; 4011 int colCount; 4012 4013 // Insert data rows 1-row-at-a-time 4014 while (true) { 4015 recStart = recEnd + csvRowDelim.length(); 4016 4017 if (recStart >= string.length()) { 4018 break; 4019 } 4020 4021 recEnd = string.indexOf(csvRowDelim, recStart); 4022 4023 if (recEnd < 0) { 4024 4025 // Last record 4026 recEnd = string.length(); 4027 } 4028 4029 colStart = recStart; 4030 colEnd = -1; 4031 colCount = 0; 4032 4033 recCount++; 4034 4035 while (true) { 4036 if (colEnd == recEnd) { 4037 4038 // We processed final column last time through loop 4039 break; 4040 } 4041 4042 colEnd = string.indexOf(csvColDelim, colStart); 4043 4044 if (colEnd < 0 || colEnd > recEnd) { 4045 colEnd = recEnd; 4046 } 4047 4048 if (colCount == dataVals.length) { 4049 throw new IOException( 4050 "Header has " + headers.length 4051 + " columns. CSV record " + recCount 4052 + " has too many column values."); 4053 } 4054 4055 dataVals[colCount++] = string.substring(colStart, colEnd); 4056 colStart = colEnd + csvColDelim.length(); 4057 } 4058 4059 if (colCount != dataVals.length) { 4060 throw new IOException("Header has " + headers.length 4061 + " columns. CSV record " 4062 + recCount + " has " + colCount 4063 + " column values."); 4064 } 4065 4066 for (int i = 0; i < dataVals.length; i++) { 4067 4068 //System.err.println("ps.setString(" + i + ", " 4069 // + dataVals[i] + ')'); 4070 ps.setString( 4071 i + 1, 4072 (((dataVals[i].length() < 1 && autonulls[i]) || dataVals[i].equals(csvNullRep)) 4073 ? null 4074 : dataVals[i])); 4075 } 4076 4077 retval = ps.executeUpdate(); 4078 4079 if (retval != 1) { 4080 curConn.rollback(); 4081 4082 throw new BadSpecial("Insert of row " + recCount 4083 + " failed. " + retval 4084 + " rows modified"); 4085 } 4086 4087 possiblyUncommitteds.set(true); 4088 } 4089 4090 stdprintln("Successfully inserted " + recCount 4091 + " rows into table '" + tableName + "'"); 4092 } catch (SQLException se) { 4093 try { 4094 curConn.rollback(); 4095 } catch (SQLException se2) {} 4096 4097 throw new BadSpecial( 4098 "SQL error encountered when inserting CSV data: " + se); 4099 } 4100 } 4101 4102}