0001: /* Copyright (c) 2001-2007, The HSQL Development Group
0002: * All rights reserved.
0003: *
0004: * Redistribution and use in source and binary forms, with or without
0005: * modification, are permitted provided that the following conditions are met:
0006: *
0007: * Redistributions of source code must retain the above copyright notice, this
0008: * list of conditions and the following disclaimer.
0009: *
0010: * Redistributions in binary form must reproduce the above copyright notice,
0011: * this list of conditions and the following disclaimer in the documentation
0012: * and/or other materials provided with the distribution.
0013: *
0014: * Neither the name of the HSQL Development Group nor the names of its
0015: * contributors may be used to endorse or promote products derived from this
0016: * software without specific prior written permission.
0017: *
0018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
0019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0021: * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
0022: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
0025: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0026: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0027: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0028: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0029: */
0030:
0031: package org.hsqldb.util;
0032:
0033: import java.io.BufferedReader;
0034: import java.io.ByteArrayOutputStream;
0035: import java.io.File;
0036: import java.io.FileInputStream;
0037: import java.io.FileNotFoundException;
0038: import java.io.FileOutputStream;
0039: import java.io.IOException;
0040: import java.io.InputStream;
0041: import java.io.InputStreamReader;
0042: import java.io.OutputStreamWriter;
0043: import java.io.PrintStream;
0044: import java.io.PrintWriter;
0045: import java.io.UnsupportedEncodingException;
0046: import java.sql.Connection;
0047: import java.sql.DatabaseMetaData;
0048: import java.sql.PreparedStatement;
0049: import java.sql.ResultSet;
0050: import java.sql.ResultSetMetaData;
0051: import java.sql.SQLException;
0052: import java.sql.Statement;
0053: import java.util.ArrayList;
0054: import java.util.HashMap;
0055: import java.util.Iterator;
0056: import java.util.List;
0057: import java.util.Set;
0058: import java.util.HashSet;
0059: import java.util.Map;
0060: import java.util.SortedMap;
0061: import java.util.TreeMap;
0062: import java.util.regex.Matcher;
0063: import java.util.regex.Pattern;
0064: import java.util.regex.PatternSyntaxException;
0065:
0066: /* $Id: SqlFile.java,v 1.169 2007/08/02 18:15:32 unsaved Exp $ */
0067:
0068: /**
0069: * Encapsulation of a sql text file like 'myscript.sql'.
0070: * The ultimate goal is to run the execute() method to feed the SQL
0071: * commands within the file to a jdbc connection.
0072: *
0073: * Some implementation comments and variable names use keywords based
0074: * on the following definitions. <UL>
0075: * <LI> COMMAND = Statement || SpecialCommand || BufferCommand
0076: * Statement = SQL statement like "SQL Statement;"
0077: * SpecialCommand = Special Command like "\x arg..."
0078: * BufferCommand = Editing/buffer command like ":s/this/that/"
0079: *
0080: * When entering SQL statements, you are always "appending" to the
0081: * "immediate" command (not the "buffer", which is a different thing).
0082: * All you can do to the immediate command is append new lines to it,
0083: * execute it, or save it to buffer.
0084: * When you are entering a buffer edit command like ":s/this/that/",
0085: * your immediate command is the buffer-edit-command. The buffer
0086: * is the command string that you are editing.
0087: * The buffer usually contains either an exact copy of the last command
0088: * executed or sent to buffer by entering a blank line,
0089: * but BUFFER commands can change the contents of the buffer.
0090: *
0091: * In general, the special commands mirror those of Postgresql's psql,
0092: * but SqlFile handles command editing much different from Postgresql
0093: * because of Java's lack of support for raw tty I/O.
0094: * The \p special command, in particular, is very different from psql's.
0095: *
0096: * Buffer commands are unique to SQLFile. The ":" commands allow
0097: * you to edit the buffer and to execute the buffer.
0098: *
0099: * \d commands are very poorly supported for Mysql because
0100: * (a) Mysql lacks most of the most basic JDBC support elements, and
0101: * the most basic role and schema features, and
0102: * (b) to access the Mysql data dictionay, one must change the database
0103: * instance (to do that would require work to restore the original state
0104: * and could have disastrous effects upon transactions).
0105: *
0106: * To make changes to this class less destructive to external callers,
0107: * the input parameters should be moved to setters (probably JavaBean
0108: * setters would be best) instead of constructor args and System
0109: * Properties.
0110: *
0111: * The process*() methods, other than processBuffHist() ALWAYS execute
0112: * on "buffer", and expect it to contain the method specific prefix
0113: * (if any).
0114: *
0115: * @version $Revision: 1.169 $
0116: * @author Blaine Simpson unsaved@users
0117: */
0118:
0119: public class SqlFile {
0120: private static final int DEFAULT_HISTORY_SIZE = 40;
0121: private File file;
0122: private boolean interactive;
0123: private String primaryPrompt = "sql> ";
0124: private String rawPrompt = null;
0125: private String contPrompt = " +> ";
0126: private Connection curConn = null;
0127: private boolean htmlMode = false;
0128: private Map userVars; // Always a non-null map set in cons.
0129: private List history = null;
0130: private int rawMode = RAW_FALSE;
0131: private String nullRepToken = null;
0132: private String dsvTargetFile = null;
0133: private String dsvTargetTable = null;
0134: private String dsvConstCols = null;
0135: private String dsvRejectFile = null;
0136: private String dsvRejectReport = null;
0137: public static String LS = System.getProperty("line.separator");
0138: private int maxHistoryLength = 1;
0139: private SqltoolRB rb = null;
0140: private String magicPrefix = null;
0141: // For append editing, this is automatically prefixed to what the
0142: // user enters.
0143:
0144: private static final int RAW_FALSE = 0; // Raw mode off
0145: private static final int RAW_EMPTY = 1; // Raw mode on, but no raw input yet
0146: private static final int RAW_DATA = 2; // Raw mode on and we have input
0147:
0148: /**
0149: * N.b. javax.util.regex Optional capture groups (...)? are completely
0150: * unpredictable wrt whether you get a null capture group vs. no capture.
0151: * Must always check count!
0152: */
0153: private static Pattern specialPattern = Pattern
0154: .compile("\\s*\\\\(\\S+)(?:\\s+(.*\\S))?\\s*");
0155: private static Pattern plPattern = Pattern
0156: .compile("\\s*\\*\\s*(.*\\S)?\\s*");
0157: private static Pattern foreachPattern = Pattern
0158: .compile("\\s*\\*\\s*foreach\\s+(\\S+)\\s*\\(([^)]*)\\)\\s*");
0159: private static Pattern ifwhilePattern = Pattern
0160: .compile("\\s*\\*\\s*\\S+\\s*\\(([^)]*)\\)\\s*");
0161: private static Pattern varsetPattern = Pattern
0162: .compile("\\s*\\*\\s*(\\S+)\\s*([=_~])\\s*(?:(.*\\S)\\s*)?");
0163: private static Pattern substitutionPattern = Pattern
0164: .compile("(\\S)(.+?)\\1(.*?)\\1(.+)?\\s*");
0165: // Note that this pattern does not include the leading ":s".
0166: private static Pattern slashHistoryPattern = Pattern
0167: .compile("\\s*/([^/]+)/\\s*(\\S.*)?");
0168: private static Pattern historyPattern = Pattern
0169: .compile("\\s*(-?\\d+)?\\s*(\\S.*)?");
0170: // Note that this pattern does not include the leading ":".
0171: private static Pattern wincmdPattern = null;
0172:
0173: static {
0174: if (System.getProperty("os.name").startsWith("Windows")) {
0175: wincmdPattern = Pattern.compile("([^\"]+)?(\"[^\"]*\")?");
0176: }
0177: }
0178:
0179: // This can throw a runtime exception, but since the pattern
0180: // Strings are constant, one test run of the program will tell
0181: // if the patterns are good.
0182:
0183: /**
0184: * Encapsulate updating local variables which depend upon PL variables.
0185: *
0186: * Right now this is called whenever the user variable map is changed.
0187: * It would be more efficient to do it JIT by keeping track of when
0188: * the vars may be "dirty" by a variable map change, and having all
0189: * methods that use the settings call a conditional updater, but that
0190: * is less reliable since there is no way to guarantee that the vars
0191: * are not used without checking.
0192: */
0193: private void updateUserSettings() {
0194: dsvSkipPrefix = SqlFile.convertEscapes((String) userVars
0195: .get("*DSV_SKIP_PREFIX"));
0196: if (dsvSkipPrefix == null) {
0197: dsvSkipPrefix = DEFAULT_SKIP_PREFIX;
0198: }
0199: dsvSkipCols = (String) userVars.get("*DSV_SKIP_COLS");
0200: dsvColDelim = SqlFile.convertEscapes((String) userVars
0201: .get("*DSV_COL_DELIM"));
0202: if (dsvColDelim == null) {
0203: dsvColDelim = SqlFile.convertEscapes((String) userVars
0204: .get("*CSV_COL_DELIM"));
0205: }
0206: if (dsvColDelim == null) {
0207: dsvColDelim = DEFAULT_COL_DELIM;
0208: }
0209:
0210: dsvRowDelim = SqlFile.convertEscapes((String) userVars
0211: .get("*DSV_ROW_DELIM"));
0212: if (dsvRowDelim == null) {
0213: dsvRowDelim = SqlFile.convertEscapes((String) userVars
0214: .get("*CSV_ROW_DELIM"));
0215: }
0216: if (dsvRowDelim == null) {
0217: dsvRowDelim = DEFAULT_ROW_DELIM;
0218: }
0219:
0220: dsvTargetFile = (String) userVars.get("*DSV_TARGET_FILE");
0221: if (dsvTargetFile == null) {
0222: dsvTargetFile = (String) userVars.get("*CSV_FILEPATH");
0223: }
0224: dsvTargetTable = (String) userVars.get("*DSV_TARGET_TABLE");
0225: if (dsvTargetTable == null) {
0226: dsvTargetTable = (String) userVars.get("*CSV_TABLENAME");
0227: // This just for legacy variable name.
0228: }
0229:
0230: dsvConstCols = (String) userVars.get("*DSV_CONST_COLS");
0231: dsvRejectFile = (String) userVars.get("*DSV_REJECT_FILE");
0232: dsvRejectReport = (String) userVars.get("*DSV_REJECT_REPORT");
0233:
0234: nullRepToken = (String) userVars.get("*NULL_REP_TOKEN");
0235: if (nullRepToken == null) {
0236: nullRepToken = (String) userVars.get("*CSV_NULL_REP");
0237: }
0238: if (nullRepToken == null) {
0239: nullRepToken = DEFAULT_NULL_REP;
0240: }
0241: }
0242:
0243: /**
0244: * Private class to "share" a variable among a family of SqlFile
0245: * instances.
0246: */
0247: private static class BooleanBucket {
0248: BooleanBucket() {
0249: }
0250:
0251: private boolean bPriv = false;
0252:
0253: public void set(boolean bIn) {
0254: bPriv = bIn;
0255: }
0256:
0257: public boolean get() {
0258: return bPriv;
0259: }
0260: }
0261:
0262: BooleanBucket possiblyUncommitteds = new BooleanBucket();
0263: // This is an imperfect solution since when user runs SQL they could
0264: // be running DDL or a commit or rollback statement. All we know is,
0265: // they MAY run some DML that needs to be committed.
0266:
0267: private static final String DIVIDER = "-----------------------------------------------------------------"
0268: + "-----------------------------------------------------------------";
0269: // Needs to be at least as wide as the widest field or header displayed.
0270: private static final String SPACES = " "
0271: + " ";
0272: // Needs to be at least as wide as the widest field or header displayed.
0273: private static String revnum = null;
0274:
0275: static {
0276: revnum = "354";
0277: }
0278:
0279: private String DSV_OPTIONS_TEXT = null;
0280: private String D_OPTIONS_TEXT = null;
0281: private String RAW_LEADIN_MSG = null;
0282:
0283: /**
0284: * Interpret lines of input file as SQL Statements, Comments,
0285: * Special Commands, and Buffer Commands.
0286: * Most Special Commands and many Buffer commands are only for
0287: * interactive use.
0288: *
0289: * @param inFile inFile of null means to read stdin.
0290: * @param inInteractive If true, prompts are printed, the interactive
0291: * Special commands are enabled, and
0292: * continueOnError defaults to true.
0293: * @throws IOException If can't open specified SQL file.
0294: */
0295: public SqlFile(File inFile, boolean inInteractive, Map inVars)
0296: throws IOException {
0297: // Set up ResourceBundle first, so that any other errors may be
0298: // reported with localized messages.
0299: try {
0300: rb = new SqltoolRB();
0301: rb.validate();
0302: rb
0303: .setMissingPosValueBehavior(ValidatingResourceBundle.NOOP_BEHAVIOR);
0304: rb
0305: .setMissingPropertyBehavior(ValidatingResourceBundle.NOOP_BEHAVIOR);
0306: } catch (RuntimeException re) {
0307: System.err.println("Failed to initialize resource bundle");
0308: throw re;
0309: }
0310: rawPrompt = rb.getString(SqltoolRB.RAWMODE_PROMPT) + "> ";
0311: DSV_OPTIONS_TEXT = rb.getString(SqltoolRB.DSV_OPTIONS);
0312: D_OPTIONS_TEXT = rb.getString(SqltoolRB.D_OPTIONS);
0313: RAW_LEADIN_MSG = rb.getString(SqltoolRB.RAW_LEADIN);
0314: DSV_X_SYNTAX_MSG = rb.getString(SqltoolRB.DSV_X_SYNTAX);
0315: DSV_M_SYNTAX_MSG = rb.getString(SqltoolRB.DSV_M_SYNTAX);
0316: nobufferYetString = rb.getString(SqltoolRB.NOBUFFER_YET);
0317:
0318: file = inFile;
0319: interactive = inInteractive;
0320: userVars = inVars;
0321: if (userVars == null) {
0322: userVars = new HashMap();
0323: }
0324: updateUserSettings();
0325:
0326: if (file != null && !file.canRead()) {
0327: throw new IOException(rb.getString(
0328: SqltoolRB.SQLFILE_READFAIL, file.toString()));
0329: }
0330: if (interactive) {
0331: history = new ArrayList();
0332: String histLenString = System
0333: .getProperty("sqltool.historyLength");
0334: if (histLenString != null)
0335: try {
0336: maxHistoryLength = Integer.parseInt(histLenString);
0337: } catch (Exception e) {
0338: }
0339: else {
0340: maxHistoryLength = DEFAULT_HISTORY_SIZE;
0341: }
0342: }
0343: }
0344:
0345: /**
0346: * Constructor for reading stdin instead of a file for commands.
0347: *
0348: * @see #SqlFile(File,boolean)
0349: */
0350: public SqlFile(boolean inInteractive, Map inVars)
0351: throws IOException {
0352: this (null, inInteractive, inVars);
0353: }
0354:
0355: /**
0356: * Process all the commands on stdin.
0357: *
0358: * @param conn The JDBC connection to use for SQL Commands.
0359: * @see #execute(Connection,PrintStream,PrintStream,boolean)
0360: */
0361: public void execute(Connection conn, Boolean coeOverride)
0362: throws SqlToolError, SQLException {
0363: execute(conn, System.out, System.err, coeOverride);
0364: }
0365:
0366: /**
0367: * Process all the commands on stdin.
0368: *
0369: * @param conn The JDBC connection to use for SQL Commands.
0370: * @see #execute(Connection,PrintStream,PrintStream,boolean)
0371: */
0372: public void execute(Connection conn, boolean coeOverride)
0373: throws SqlToolError, SQLException {
0374: execute(conn, System.out, System.err, new Boolean(coeOverride));
0375: }
0376:
0377: // So we can tell how to handle quit and break commands.
0378: public boolean recursed = false;
0379: private String lastSqlStatement = null;
0380: private int curLinenum = -1;
0381: private PrintStream psStd = null;
0382: private PrintStream psErr = null;
0383: private PrintWriter pwQuery = null;
0384: private PrintWriter pwDsv = null;
0385: StringBuffer immCmdSB = new StringBuffer();
0386: private boolean continueOnError = false;
0387: /*
0388: * This is reset upon each execute() invocation (to true if interactive,
0389: * false otherwise).
0390: */
0391: private static final String DEFAULT_CHARSET = null;
0392: // Change to Charset.defaultCharset().name(); once we can use Java 1.5!
0393: private BufferedReader br = null;
0394: private String charset = null;
0395: private String buffer = null;
0396:
0397: /**
0398: * Process all the commands in the file (or stdin) associated with
0399: * "this" object.
0400: * Run SQL in the file through the given database connection.
0401: *
0402: * This is synchronized so that I can use object variables to keep
0403: * track of current line number, command, connection, i/o streams, etc.
0404: *
0405: * Sets encoding character set to that specified with System Property
0406: * 'sqlfile.charset'. Defaults to "US-ASCII".
0407: *
0408: * @param conn The JDBC connection to use for SQL Commands.
0409: * @throws SQLExceptions thrown by JDBC driver.
0410: * Only possible if in "\c false" mode.
0411: * @throws SqlToolError all other errors.
0412: * This includes including QuitNow, BreakException,
0413: * ContinueException for recursive calls only.
0414: */
0415: public synchronized void execute(Connection conn,
0416: PrintStream stdIn, PrintStream errIn, Boolean coeOverride)
0417: throws SqlToolError, SQLException {
0418: psStd = stdIn;
0419: psErr = errIn;
0420: curConn = conn;
0421: curLinenum = -1;
0422:
0423: String inputLine;
0424: String trimmedInput;
0425: String deTerminated;
0426: boolean inComment = false; // Gobbling up a comment
0427: int postCommentIndex;
0428: boolean rollbackUncoms = true;
0429:
0430: continueOnError = (coeOverride == null) ? interactive
0431: : coeOverride.booleanValue();
0432:
0433: if (userVars.size() > 0) {
0434: plMode = true;
0435: }
0436:
0437: String specifiedCharSet = System.getProperty("sqlfile.charset");
0438:
0439: charset = ((specifiedCharSet == null) ? DEFAULT_CHARSET
0440: : specifiedCharSet);
0441:
0442: try {
0443: br = new BufferedReader(
0444: (charset == null) ? (new InputStreamReader(
0445: (file == null) ? System.in
0446: : (new FileInputStream(file))))
0447: : (new InputStreamReader(
0448: ((file == null) ? System.in
0449: : (new FileInputStream(file))),
0450: charset)));
0451: // Replace with just "(new FileInputStream(file), charset)"
0452: // once use defaultCharset from Java 1.5 in charset init. above.
0453: curLinenum = 0;
0454:
0455: if (interactive) {
0456: stdprintln(rb.getString(SqltoolRB.SQLFILE_BANNER,
0457: revnum));
0458: }
0459:
0460: while (true) {
0461: if (interactive && magicPrefix == null) {
0462: psStd
0463: .print((immCmdSB.length() > 0 || rawMode == RAW_DATA) ? contPrompt
0464: : ((rawMode == RAW_FALSE) ? primaryPrompt
0465: : rawPrompt));
0466: }
0467:
0468: inputLine = br.readLine();
0469: if (magicPrefix != null) {
0470: inputLine = magicPrefix + inputLine;
0471: magicPrefix = null;
0472: }
0473:
0474: if (inputLine == null) {
0475: /*
0476: * This is because interactive EOD on some OSes doesn't
0477: * send a line-break, resulting in no linebreak at all
0478: * after the SqlFile prompt or whatever happens to be
0479: * on their screen.
0480: */
0481: if (interactive) {
0482: psStd.println();
0483: }
0484:
0485: break;
0486: }
0487:
0488: curLinenum++;
0489:
0490: if (inComment) {
0491: postCommentIndex = inputLine.indexOf("*/") + 2;
0492:
0493: if (postCommentIndex > 1) {
0494: // I see no reason to leave comments in history.
0495: inputLine = inputLine
0496: .substring(postCommentIndex);
0497:
0498: // Empty the buffer. The non-comment remainder of
0499: // this line is either the beginning of a new SQL
0500: // or Special command, or an empty line.
0501: immCmdSB.setLength(0);
0502:
0503: inComment = false;
0504: } else {
0505: // Just completely ignore the input line.
0506: continue;
0507: }
0508: }
0509:
0510: trimmedInput = inputLine.trim();
0511:
0512: try {
0513: if (rawMode != RAW_FALSE) {
0514: boolean rawExecute = inputLine.equals(".;");
0515: if (rawExecute || inputLine.equals(":.")) {
0516: if (rawMode == RAW_EMPTY) {
0517: rawMode = RAW_FALSE;
0518: throw new SqlToolError(rb
0519: .getString(SqltoolRB.RAW_EMPTY));
0520: }
0521: rawMode = RAW_FALSE;
0522:
0523: setBuf(immCmdSB.toString());
0524: immCmdSB.setLength(0);
0525:
0526: if (rawExecute) {
0527: historize();
0528: processSQL();
0529: } else if (interactive) {
0530: stdprintln(rb
0531: .getString(SqltoolRB.RAW_MOVEDTOBUFFER));
0532: }
0533: } else {
0534: if (rawMode == RAW_DATA) {
0535: immCmdSB.append('\n');
0536: }
0537: rawMode = RAW_DATA;
0538:
0539: if (inputLine.length() > 0) {
0540: immCmdSB.append(inputLine);
0541: }
0542: }
0543:
0544: continue;
0545: }
0546:
0547: if (immCmdSB.length() == 0) {
0548: // NEW Immediate Command (i.e., not appending).
0549: if (trimmedInput.startsWith("/*")) {
0550: postCommentIndex = trimmedInput.indexOf(
0551: "*/", 2) + 2;
0552:
0553: if (postCommentIndex > 1) {
0554: // I see no reason to leave comments in
0555: // history.
0556: inputLine = inputLine
0557: .substring(postCommentIndex
0558: + inputLine.length()
0559: - trimmedInput.length());
0560: trimmedInput = inputLine.trim();
0561: } else {
0562: // Just so we get continuation lines:
0563: immCmdSB.append("COMMENT");
0564:
0565: inComment = true;
0566:
0567: continue;
0568: }
0569: }
0570:
0571: if (trimmedInput.length() == 0) {
0572: // This is just to filter out useless newlines at
0573: // beginning of commands.
0574: continue;
0575: }
0576:
0577: if ((trimmedInput.charAt(0) == '*' && (trimmedInput
0578: .length() < 2 || trimmedInput.charAt(1) != '{'))
0579: || trimmedInput.charAt(0) == '\\') {
0580: setBuf(trimmedInput);
0581: processFromBuffer();
0582: continue;
0583: }
0584:
0585: if (trimmedInput.charAt(0) == ':'
0586: && interactive) {
0587: processBuffHist(trimmedInput.substring(1));
0588: continue;
0589: }
0590:
0591: String ucased = trimmedInput.toUpperCase();
0592:
0593: if (ucased.startsWith("DECLARE")
0594: || ucased.startsWith("BEGIN")) {
0595: rawMode = RAW_EMPTY;
0596:
0597: immCmdSB.append(inputLine);
0598:
0599: if (interactive) {
0600: stdprintln(RAW_LEADIN_MSG);
0601: }
0602:
0603: continue;
0604: }
0605: }
0606:
0607: if (trimmedInput.length() == 0 && interactive
0608: && !inComment) {
0609: // Blank lines delimit commands ONLY IN INTERACTIVE
0610: // MODE!
0611: setBuf(immCmdSB.toString());
0612: immCmdSB.setLength(0);
0613: stdprintln(rb
0614: .getString(SqltoolRB.INPUT_MOVEDTOBUFFER));
0615: continue;
0616: }
0617:
0618: deTerminated = SqlFile.deTerminated(inputLine);
0619:
0620: // A null terminal line (i.e., /\s*;\s*$/) is never useful.
0621: if (!trimmedInput.equals(";")) {
0622: if (immCmdSB.length() > 0) {
0623: immCmdSB.append('\n');
0624: }
0625:
0626: immCmdSB
0627: .append((deTerminated == null) ? inputLine
0628: : deTerminated);
0629: }
0630:
0631: if (deTerminated == null) {
0632: continue;
0633: }
0634:
0635: // If we reach here, then immCmdSB contains a complete
0636: // SQL command.
0637:
0638: if (immCmdSB.toString().trim().length() == 0) {
0639: immCmdSB.setLength(0);
0640: throw new SqlToolError(
0641: rb
0642: .getString(SqltoolRB.SQLSTATEMENT_EMPTY));
0643: // There is nothing inherently wrong with issuing
0644: // an empty command, like to test DB server health.
0645: // But, this check effectively catches many syntax
0646: // errors early, and the DB check can be done by
0647: // sending a comment like "// comment".
0648: }
0649:
0650: setBuf(immCmdSB.toString());
0651: immCmdSB.setLength(0);
0652: historize();
0653: processSQL();
0654: } catch (BadSpecial bs) {
0655: // BadSpecials ALWAYS have non-null getMessage().
0656: errprintln(rb.getString(SqltoolRB.ERRORAT,
0657: new String[] {
0658: ((file == null) ? "stdin" : file
0659: .toString()),
0660: Integer.toString(curLinenum),
0661: inputLine, bs.getMessage(), }));
0662: Throwable cause = bs.getCause();
0663: if (cause != null) {
0664: errprintln(rb.getString(SqltoolRB.CAUSEREPORT,
0665: cause.toString()));
0666:
0667: }
0668:
0669: if (!continueOnError) {
0670: throw new SqlToolError(bs);
0671: }
0672: } catch (SQLException se) {
0673: errprintln("SQL "
0674: + rb
0675: .getString(
0676: SqltoolRB.ERRORAT,
0677: new String[] {
0678: ((file == null) ? "stdin"
0679: : file
0680: .toString()),
0681: Integer
0682: .toString(curLinenum),
0683: lastSqlStatement,
0684: se.getMessage(), }));
0685: // It's possible that we could have
0686: // SQLException.getMessage() == null, but if so, I think
0687: // it reasonsable to show "null". That's a DB inadequacy.
0688:
0689: if (!continueOnError) {
0690: throw se;
0691: }
0692: } catch (BreakException be) {
0693: String msg = be.getMessage();
0694:
0695: if (recursed) {
0696: rollbackUncoms = false;
0697: // Recursion level will exit by rethrowing the BE.
0698: // We set rollbackUncoms to false because only the
0699: // top level should detect break errors and
0700: // possibly roll back.
0701: } else if (msg == null || msg.equals("file")) {
0702: break;
0703: } else {
0704: errprintln(rb.getString(
0705: SqltoolRB.BREAK_UNSATISFIED, msg));
0706: }
0707:
0708: if (recursed || !continueOnError) {
0709: throw be;
0710: }
0711: } catch (ContinueException ce) {
0712: String msg = ce.getMessage();
0713:
0714: if (recursed) {
0715: rollbackUncoms = false;
0716: } else {
0717: errprintln(rb.getString(
0718: SqltoolRB.CONTINUE_UNSATISFIED, msg));
0719: }
0720:
0721: if (recursed || !continueOnError) {
0722: throw ce;
0723: }
0724: } catch (QuitNow qn) {
0725: throw qn;
0726: } catch (SqlToolError ste) {
0727: errprint(rb.getString(SqltoolRB.ERRORAT,
0728: new String[] {
0729: ((file == null) ? "stdin" : file
0730: .toString()),
0731: Integer.toString(curLinenum),
0732: inputLine,
0733: ((ste.getMessage() == null) ? ""
0734: : ste.getMessage()) }));
0735: if (ste.getMessage() != null)
0736: errprintln("");
0737: Throwable cause = ste.getCause();
0738: if (cause != null) {
0739: errprintln(rb.getString(SqltoolRB.CAUSEREPORT,
0740: cause.toString()));
0741: }
0742: if (!continueOnError) {
0743: throw ste;
0744: }
0745: }
0746:
0747: immCmdSB.setLength(0);
0748: }
0749:
0750: if (inComment || immCmdSB.length() != 0) {
0751: errprintln(rb.getString(SqltoolRB.INPUT_UNTERMINATED,
0752: immCmdSB.toString()));
0753: throw new SqlToolError(rb.getString(
0754: SqltoolRB.INPUT_UNTERMINATED, immCmdSB
0755: .toString()));
0756: }
0757:
0758: rollbackUncoms = false;
0759: // Exiting gracefully, so don't roll back.
0760: } catch (IOException ioe) {
0761: throw new SqlToolError(rb
0762: .getString(SqltoolRB.PRIMARYINPUT_ACCESSFAIL), ioe);
0763: } catch (QuitNow qn) {
0764: if (recursed) {
0765: throw qn;
0766: // Will rollback if conditions otherwise require.
0767: // Otherwise top level will decide based upon qn.getMessage().
0768: }
0769: rollbackUncoms = (qn.getMessage() != null);
0770:
0771: if (rollbackUncoms) {
0772: errprintln(rb.getString(SqltoolRB.ABORTING, qn
0773: .getMessage()));
0774: throw new SqlToolError(qn.getMessage());
0775: }
0776:
0777: return;
0778: } finally {
0779: closeQueryOutputStream();
0780:
0781: if (fetchingVar != null) {
0782: errprintln(rb.getString(SqltoolRB.PLVAR_SET_INCOMPLETE,
0783: fetchingVar));
0784: rollbackUncoms = true;
0785: }
0786:
0787: if (br != null)
0788: try {
0789: br.close();
0790: } catch (IOException ioe) {
0791: throw new SqlToolError(
0792: rb
0793: .getString(SqltoolRB.INPUTREADER_CLOSEFAIL),
0794: ioe);
0795: }
0796:
0797: if (rollbackUncoms && possiblyUncommitteds.get()) {
0798: errprintln(rb.getString(SqltoolRB.ROLLINGBACK));
0799: curConn.rollback();
0800: possiblyUncommitteds.set(false);
0801: }
0802: }
0803: }
0804:
0805: /**
0806: * Returns a copy of given string without a terminating semicolon.
0807: * If there is no terminating semicolon, null is returned.
0808: *
0809: * @param inString Base String, which will not be modified (because
0810: * a "copy" will be returned).
0811: * @returns Null if inString contains no terminating semi-colon.
0812: */
0813: private static String deTerminated(String inString) {
0814: int index = inString.lastIndexOf(';');
0815:
0816: if (index < 0) {
0817: return null;
0818: }
0819:
0820: for (int i = index + 1; i < inString.length(); i++) {
0821: if (!Character.isWhitespace(inString.charAt(i))) {
0822: return null;
0823: }
0824: }
0825:
0826: return inString.substring(0, index);
0827: }
0828:
0829: /**
0830: * Utility nested Exception class for internal use only.
0831: *
0832: * Do not instantiate with null message.
0833: */
0834: static private class BadSpecial extends AppendableException {
0835: static final long serialVersionUID = 7162440064026570590L;
0836:
0837: BadSpecial(String s) {
0838: super (s);
0839: if (s == null)
0840: throw new RuntimeException(
0841: "Must construct BadSpecials with non-null message");
0842: }
0843:
0844: BadSpecial(String s, Throwable t) {
0845: super (s, t);
0846: if (s == null)
0847: throw new RuntimeException(
0848: "Must construct BadSpecials with non-null message");
0849: }
0850: }
0851:
0852: /**
0853: * Utility nested Exception class for internal use.
0854: * This must extend SqlToolError because it has to percolate up from
0855: * recursions of SqlTool.execute(), yet SqlTool.execute() is public.
0856: * Therefore, external users have no reason to specifically handle
0857: * QuitNow.
0858: */
0859: private class QuitNow extends SqlToolError {
0860: static final long serialVersionUID = 1811094258670900488L;
0861:
0862: public QuitNow(String s) {
0863: super (s);
0864: }
0865:
0866: public QuitNow() {
0867: super ();
0868: }
0869: }
0870:
0871: /**
0872: * Utility nested Exception class for internal use.
0873: * This must extend SqlToolError because it has to percolate up from
0874: * recursions of SqlTool.execute(), yet SqlTool.execute() is public.
0875: * Therefore, external users have no reason to specifically handle
0876: * BreakException.
0877: */
0878: private class BreakException extends SqlToolError {
0879: static final long serialVersionUID = 351150072817675994L;
0880:
0881: public BreakException() {
0882: super ();
0883: }
0884:
0885: public BreakException(String s) {
0886: super (s);
0887: }
0888: }
0889:
0890: /**
0891: * Utility nested Exception class for internal use.
0892: * This must extend SqlToolError because it has to percolate up from
0893: * recursions of SqlTool.execute(), yet SqlTool.execute() is public.
0894: * Therefore, external users have no reason to specifically handle
0895: * ContinueException.
0896: */
0897: private class ContinueException extends SqlToolError {
0898: static final long serialVersionUID = 5064604160827106014L;
0899:
0900: public ContinueException() {
0901: super ();
0902: }
0903:
0904: public ContinueException(String s) {
0905: super (s);
0906: }
0907: }
0908:
0909: /**
0910: * Utility nested Exception class for internal use only.
0911: */
0912: private class BadSubst extends Exception {
0913: static final long serialVersionUID = 7325933736897253269L;
0914:
0915: BadSubst(String s) {
0916: super (s);
0917: }
0918: }
0919:
0920: /**
0921: * Utility nested Exception class for internal use only.
0922: */
0923: private class RowError extends AppendableException {
0924: static final long serialVersionUID = 754346434606022750L;
0925:
0926: RowError(String s) {
0927: super (s);
0928: }
0929:
0930: RowError(Throwable t) {
0931: this (null, t);
0932: }
0933:
0934: RowError(String s, Throwable t) {
0935: super (s, t);
0936: }
0937: }
0938:
0939: /**
0940: * Execute processSql/processPL/processSpecial from buffer.
0941: */
0942: public void processFromBuffer() throws BadSpecial, SQLException,
0943: SqlToolError {
0944: historize();
0945: if (buffer.charAt(0) == '*'
0946: && (buffer.length() < 2 || buffer.charAt(1) != '{')) {
0947: // Test above just means commands starting with *, EXCEPT
0948: // for commands beginning with *{.
0949: processPL(buffer);
0950: return;
0951: }
0952:
0953: if (buffer.charAt(0) == '\\') {
0954: processSpecial(buffer);
0955: return;
0956: }
0957: processSQL();
0958: }
0959:
0960: /**
0961: * Process a Buffer/History Command.
0962: *
0963: * Due to the nature of the goal here, we don't trim() "other" like
0964: * we do for other kinds of commands.
0965: *
0966: * @param inString Complete command, less the leading ':' character.
0967: * @throws SQLException thrown by JDBC driver.
0968: * @throws BadSpecial special-command-specific errors.
0969: * @throws SqlToolError all other errors.
0970: */
0971: private void processBuffHist(String inString) throws BadSpecial,
0972: SQLException, SqlToolError {
0973: if (inString.length() < 1) {
0974: throw new BadSpecial(rb
0975: .getString(SqltoolRB.BUFHIST_UNSPECIFIED));
0976: }
0977:
0978: // First handle the simple cases where user may not specify a
0979: // command number.
0980: char commandChar = inString.charAt(0);
0981: String other = inString.substring(1);
0982: if (other.trim().length() == 0) {
0983: other = null;
0984: }
0985: switch (commandChar) {
0986: case 'l':
0987: case 'b':
0988: enforce1charBH(other, 'l');
0989: if (buffer == null) {
0990: stdprintln(nobufferYetString);
0991: } else {
0992: stdprintln(rb.getString(SqltoolRB.EDITBUFFER_CONTENTS,
0993: buffer));
0994: }
0995:
0996: return;
0997:
0998: case 'h':
0999: enforce1charBH(other, 'h');
1000: showHistory();
1001:
1002: return;
1003:
1004: case '?':
1005: stdprintln(rb.getString(SqltoolRB.BUFFER_HELP));
1006:
1007: return;
1008: }
1009:
1010: Integer histNum = null;
1011: Matcher hm = slashHistoryPattern.matcher(inString);
1012: if (hm.matches()) {
1013: histNum = historySearch(hm.group(1));
1014: if (histNum == null) {
1015: stdprintln(rb.getString(SqltoolRB.SUBSTITUTION_NOMATCH));
1016: return;
1017: }
1018: } else {
1019: hm = historyPattern.matcher(inString);
1020: if (!hm.matches()) {
1021: throw new BadSpecial(rb
1022: .getString(SqltoolRB.EDIT_MALFORMAT));
1023: // Empirically, I find that his pattern always captures two
1024: // groups. Unfortunately, there's no way to guarantee that :( .
1025: }
1026: histNum = ((hm.group(1) == null || hm.group(1).length() < 1) ? null
1027: : new Integer(hm.group(1)));
1028: }
1029: if (hm.groupCount() != 2) {
1030: throw new BadSpecial(rb.getString(SqltoolRB.EDIT_MALFORMAT));
1031: // Empirically, I find that his pattern always captures two
1032: // groups. Unfortunately, there's no way to guarantee that :( .
1033: }
1034: commandChar = ((hm.group(2) == null || hm.group(2).length() < 1) ? '\0'
1035: : hm.group(2).charAt(0));
1036: other = ((commandChar == '\0') ? null : hm.group(2)
1037: .substring(1));
1038: if (other != null && other.length() < 1)
1039: other = null;
1040: String targetCommand = ((histNum == null) ? null
1041: : commandFromHistory(histNum.intValue()));
1042: // Every command below depends upon buffer content.
1043:
1044: switch (commandChar) {
1045: case '\0': // Special token set above. Just history recall.
1046: setBuf(targetCommand);
1047: stdprintln(rb.getString(SqltoolRB.BUFFER_RESTORED, buffer));
1048: return;
1049:
1050: case ';':
1051: enforce1charBH(other, ';');
1052:
1053: if (targetCommand != null)
1054: setBuf(targetCommand);
1055: if (buffer == null)
1056: throw new BadSpecial(rb
1057: .getString(SqltoolRB.NOBUFFER_YET));
1058: stdprintln(rb.getString(SqltoolRB.BUFFER_EXECUTING, buffer));
1059: processFromBuffer();
1060:
1061: return;
1062:
1063: case 'a':
1064: if (targetCommand == null)
1065: targetCommand = buffer;
1066: if (targetCommand == null)
1067: throw new BadSpecial(rb
1068: .getString(SqltoolRB.NOBUFFER_YET));
1069: immCmdSB.append(targetCommand);
1070:
1071: if (other != null) {
1072: String deTerminated = SqlFile.deTerminated(other);
1073:
1074: if (!other.equals(";")) {
1075: immCmdSB.append(((deTerminated == null) ? other
1076: : deTerminated));
1077: }
1078:
1079: if (deTerminated != null) {
1080: // If we reach here, then immCmdSB contains a
1081: // complete command.
1082:
1083: setBuf(immCmdSB.toString());
1084: immCmdSB.setLength(0);
1085: stdprintln(rb.getString(SqltoolRB.BUFFER_EXECUTING,
1086: buffer));
1087: processFromBuffer();
1088:
1089: return;
1090: }
1091: }
1092:
1093: magicPrefix = immCmdSB.toString();
1094: immCmdSB.setLength(0);
1095: if (interactive)
1096: stdprint(magicPrefix);
1097:
1098: return;
1099:
1100: case 'w':
1101: if (targetCommand == null)
1102: targetCommand = buffer;
1103: if (targetCommand == null)
1104: throw new BadSpecial(rb
1105: .getString(SqltoolRB.NOBUFFER_YET));
1106: if (other == null) {
1107: throw new BadSpecial(rb
1108: .getString(SqltoolRB.DESTFILE_DEMAND));
1109: }
1110: String targetFile = dereference(other.trim(), false);
1111: // Dereference and trim the target file name
1112: // This is the only case where we dereference a : command.
1113:
1114: PrintWriter pw = null;
1115: try {
1116: pw = new PrintWriter(
1117: (charset == null) ? (new OutputStreamWriter(
1118: new FileOutputStream(targetFile, true)))
1119: : (new OutputStreamWriter(
1120: new FileOutputStream(
1121: targetFile, true),
1122: charset))
1123: // Appendmode so can append to an SQL script.
1124: );
1125: // Replace with just "(new FileOutputStream(file), charset)"
1126: // once use defaultCharset from Java 1.5 in charset init.
1127: // above.
1128:
1129: pw.print(targetCommand);
1130: if (!targetCommand.matches("\\s*[*:\\\\].*"))
1131: pw.print(';');
1132: pw.println();
1133: pw.flush();
1134: } catch (Exception e) {
1135: throw new BadSpecial(rb.getString(
1136: SqltoolRB.FILE_APPENDFAIL, targetFile), e);
1137: } finally {
1138: if (pw != null)
1139: pw.close();
1140: }
1141:
1142: return;
1143:
1144: case 's':
1145: boolean modeExecute = false;
1146: boolean modeGlobal = false;
1147: if (targetCommand == null)
1148: targetCommand = buffer;
1149: if (targetCommand == null)
1150: throw new BadSpecial(rb
1151: .getString(SqltoolRB.NOBUFFER_YET));
1152:
1153: try {
1154: if (other == null || other.length() < 3) {
1155: throw new BadSubst(
1156: rb
1157: .getString(SqltoolRB.SUBSTITUTION_MALFORMAT));
1158: }
1159: Matcher m = substitutionPattern.matcher(other);
1160: if (!m.matches()) {
1161: throw new BadSubst(
1162: rb
1163: .getString(SqltoolRB.SUBSTITUTION_MALFORMAT));
1164: }
1165:
1166: // Note that this pattern does not include the leading :.
1167: if (m.groupCount() < 3 || m.groupCount() > 4) {
1168: // Assertion failed
1169: throw new RuntimeException(
1170: "Matched substitution pattern, but "
1171: + "captured " + m.groupCount()
1172: + " groups");
1173: }
1174: String optionGroup = ((m.groupCount() > 3 && m.group(4) != null) ? (new String(
1175: m.group(4)))
1176: : null);
1177:
1178: if (optionGroup != null) {
1179: if (optionGroup.indexOf(';') > -1) {
1180: modeExecute = true;
1181: optionGroup = optionGroup.replaceFirst(";", "");
1182: }
1183: if (optionGroup.indexOf('g') > -1) {
1184: modeGlobal = true;
1185: optionGroup = optionGroup.replaceFirst("g", "");
1186: }
1187: }
1188:
1189: Matcher bufferMatcher = Pattern.compile(
1190: "(?s"
1191: + ((optionGroup == null) ? ""
1192: : optionGroup) + ')'
1193: + m.group(2)).matcher(targetCommand);
1194: String newBuffer = (modeGlobal ? bufferMatcher
1195: .replaceAll(m.group(3)) : bufferMatcher
1196: .replaceFirst(m.group(3)));
1197: if (newBuffer.equals(targetCommand)) {
1198: stdprintln(rb
1199: .getString(SqltoolRB.SUBSTITUTION_NOMATCH));
1200: return;
1201: }
1202:
1203: setBuf(newBuffer);
1204: stdprintln(rb.getString(
1205: (modeExecute ? SqltoolRB.BUFFER_EXECUTING
1206: : SqltoolRB.EDITBUFFER_CONTENTS),
1207: buffer));
1208: } catch (PatternSyntaxException pse) {
1209: throw new BadSpecial(rb
1210: .getString(SqltoolRB.SUBSTITUTION_SYNTAX), pse);
1211: } catch (BadSubst badswitch) {
1212: throw new BadSpecial(rb
1213: .getString(SqltoolRB.SUBSTITUTION_SYNTAX));
1214: }
1215:
1216: if (modeExecute) {
1217: immCmdSB.setLength(0);
1218: processFromBuffer();
1219: }
1220:
1221: return;
1222: }
1223:
1224: throw new BadSpecial(rb.getString(SqltoolRB.BUFFER_UNKNOWN,
1225: Character.toString(commandChar)));
1226: }
1227:
1228: private boolean doPrepare = false;
1229: private String prepareVar = null;
1230: private String dsvColDelim = null;
1231: private String dsvSkipPrefix = null;
1232: private String dsvRowDelim = null;
1233: private String dsvSkipCols = null;
1234: private String DSV_X_SYNTAX_MSG = null;
1235: private String DSV_M_SYNTAX_MSG = null;
1236: private String nobufferYetString = null;
1237:
1238: private void enforce1charSpecial(String token, char command)
1239: throws BadSpecial {
1240: if (token.length() != 1) {
1241: throw new BadSpecial(rb.getString(
1242: SqltoolRB.SPECIAL_EXTRACHARS, Character
1243: .toString(command), token.substring(1)));
1244: }
1245: }
1246:
1247: private void enforce1charBH(String token, char command)
1248: throws BadSpecial {
1249: if (token != null) {
1250: throw new BadSpecial(rb.getString(
1251: SqltoolRB.BUFFER_EXTRACHARS, Character
1252: .toString(command), token));
1253: }
1254: }
1255:
1256: /**
1257: * Process a Special Command.
1258: *
1259: * @param inString TRIMMED complete command, including the leading
1260: * '\' character.
1261: * @throws SQLException thrown by JDBC driver.
1262: * @throws BadSpecial special-command-specific errors.
1263: * @throws SqlToolError all other errors, plus QuitNow,
1264: * BreakException, ContinueException.
1265: */
1266: private void processSpecial(String inString) throws BadSpecial,
1267: QuitNow, SQLException, SqlToolError {
1268: if (inString.equals("\\")) {
1269: throw new BadSpecial(rb
1270: .getString(SqltoolRB.SPECIAL_UNSPECIFIED));
1271: }
1272: Matcher m = specialPattern.matcher(plMode ? dereference(
1273: inString, false) : inString);
1274: if (!m.matches()) {
1275: throw new BadSpecial(rb
1276: .getString(SqltoolRB.SPECIAL_MALFORMAT));
1277: // I think it's impossible to get here, since the pattern is
1278: // so liberal.
1279: }
1280: if (m.groupCount() < 1 || m.groupCount() > 2) {
1281: // Failed assertion
1282: throw new RuntimeException("Pattern matched, yet captured "
1283: + m.groupCount() + " groups");
1284: }
1285:
1286: String arg1 = m.group(1);
1287: String other = ((m.groupCount() > 1) ? m.group(2) : null);
1288:
1289: switch (arg1.charAt(0)) {
1290: case 'q':
1291: enforce1charSpecial(arg1, 'q');
1292: if (other != null) {
1293: throw new QuitNow(other);
1294: }
1295:
1296: throw new QuitNow();
1297: case 'H':
1298: enforce1charSpecial(arg1, 'H');
1299: htmlMode = !htmlMode;
1300:
1301: stdprintln(rb.getString(SqltoolRB.HTML_MODE, Boolean
1302: .toString(htmlMode)));
1303:
1304: return;
1305:
1306: case 'm':
1307: if (arg1.equals("m?")
1308: || (arg1.equals("m") && other != null && other
1309: .equals("?"))) {
1310: stdprintln(DSV_OPTIONS_TEXT + LS + DSV_M_SYNTAX_MSG);
1311: return;
1312: }
1313: if (arg1.length() != 1 || other == null) {
1314: throw new BadSpecial(DSV_M_SYNTAX_MSG);
1315: }
1316: boolean noComments = other.charAt(other.length() - 1) == '*';
1317: String skipPrefix = null;
1318:
1319: if (noComments) {
1320: other = other.substring(0, other.length() - 1).trim();
1321: if (other.length() < 1) {
1322: throw new BadSpecial(DSV_M_SYNTAX_MSG);
1323: }
1324: } else {
1325: skipPrefix = dsvSkipPrefix;
1326: }
1327: int colonIndex = other.indexOf(" :");
1328: if (colonIndex > -1 && colonIndex < other.length() - 2) {
1329: skipPrefix = other.substring(colonIndex + 2);
1330: other = other.substring(0, colonIndex).trim();
1331: }
1332:
1333: importDsv(other, skipPrefix);
1334:
1335: return;
1336:
1337: case 'x':
1338: if (arg1.equals("x?")
1339: || (arg1.equals("x") && other != null && other
1340: .equals("?"))) {
1341: stdprintln(DSV_OPTIONS_TEXT + LS + DSV_X_SYNTAX_MSG);
1342: return;
1343: }
1344: try {
1345: if (arg1.length() != 1 || other == null) {
1346: throw new BadSpecial(DSV_X_SYNTAX_MSG);
1347: }
1348:
1349: String tableName = ((other.indexOf(' ') > 0) ? null
1350: : other);
1351:
1352: if (dsvTargetFile == null && tableName == null) {
1353: throw new BadSpecial(rb
1354: .getString(SqltoolRB.DSV_TARGETFILE_DEMAND));
1355: }
1356: File dsvFile = new File(
1357: (dsvTargetFile == null) ? (tableName + ".dsv")
1358: : dsvTargetFile);
1359:
1360: pwDsv = new PrintWriter(
1361: (charset == null) ? (new OutputStreamWriter(
1362: new FileOutputStream(dsvFile)))
1363: : (new OutputStreamWriter(
1364: new FileOutputStream(dsvFile),
1365: charset)));
1366: // Replace with just "(new FileOutputStream(file), charset)"
1367: // once use defaultCharset from Java 1.5 in charset init.
1368: // above.
1369:
1370: ResultSet rs = curConn.createStatement().executeQuery(
1371: (tableName == null) ? other
1372: : ("SELECT * FROM " + tableName));
1373: List colList = new ArrayList();
1374: int[] incCols = null;
1375: if (dsvSkipCols != null) {
1376: Set skipCols = new HashSet();
1377: String[] skipColsArray = dsvSkipCols
1378: .split("\\s*\\Q" + dsvColDelim + "\\E\\s*");
1379: for (int i = 0; i < skipColsArray.length; i++) {
1380: skipCols.add(skipColsArray[i].toLowerCase());
1381: }
1382: ResultSetMetaData rsmd = rs.getMetaData();
1383: for (int i = 1; i <= rsmd.getColumnCount(); i++) {
1384: if (!skipCols.remove(rsmd.getColumnName(i)
1385: .toLowerCase())) {
1386: colList.add(new Integer(i));
1387: }
1388: }
1389: if (colList.size() < 1) {
1390: throw new BadSpecial(rb.getString(
1391: SqltoolRB.DSV_NOCOLSLEFT, dsvSkipCols));
1392: }
1393: if (skipCols.size() > 0) {
1394: throw new BadSpecial(rb.getString(
1395: SqltoolRB.DSV_SKIPCOLS_MISSING,
1396: skipCols.toString()));
1397: }
1398: incCols = new int[colList.size()];
1399: for (int i = 0; i < incCols.length; i++) {
1400: incCols[i] = ((Integer) colList.get(i))
1401: .intValue();
1402: }
1403: }
1404: displayResultSet(null, rs, incCols, null);
1405: pwDsv.flush();
1406: stdprintln(rb
1407: .getString(SqltoolRB.FILE_WROTECHARS, Long
1408: .toString(dsvFile.length()), dsvFile
1409: .toString()));
1410: } catch (FileNotFoundException e) {
1411: throw new BadSpecial(rb.getString(
1412: SqltoolRB.FILE_WRITEFAIL, other), e);
1413: } catch (UnsupportedEncodingException e) {
1414: throw new BadSpecial(rb.getString(
1415: SqltoolRB.FILE_WRITEFAIL, other), e);
1416: } finally {
1417: // Reset all state changes
1418: if (pwDsv != null) {
1419: pwDsv.close();
1420: }
1421:
1422: pwDsv = null;
1423: }
1424:
1425: return;
1426:
1427: case 'd':
1428: if (arg1.equals("d?")
1429: || (arg1.equals("d") && other != null && other
1430: .equals("?"))) {
1431: stdprintln(D_OPTIONS_TEXT);
1432: return;
1433: }
1434: if (arg1.length() == 2) {
1435: listTables(arg1.charAt(1), other);
1436:
1437: return;
1438: }
1439:
1440: if (arg1.length() == 1 && other != null)
1441: try {
1442: int space = other.indexOf(' ');
1443:
1444: if (space < 0) {
1445: describe(other, null);
1446: } else {
1447: describe(other.substring(0, space), other
1448: .substring(space + 1).trim());
1449: }
1450:
1451: return;
1452: } catch (SQLException se) {
1453: throw new BadSpecial(rb
1454: .getString(SqltoolRB.METADATA_FETCH_FAIL),
1455: se);
1456: }
1457:
1458: throw new BadSpecial(rb.getString(SqltoolRB.SPECIAL_D_LIKE));
1459: case 'o':
1460: enforce1charSpecial(arg1, 'o');
1461: if (other == null) {
1462: if (pwQuery == null) {
1463: throw new BadSpecial(
1464: rb
1465: .getString(SqltoolRB.OUTPUTFILE_NONETOCLOSE));
1466: }
1467:
1468: closeQueryOutputStream();
1469:
1470: return;
1471: }
1472:
1473: if (pwQuery != null) {
1474: stdprintln(rb.getString(SqltoolRB.OUTPUTFILE_REOPENING));
1475: closeQueryOutputStream();
1476: }
1477:
1478: try {
1479: pwQuery = new PrintWriter(
1480: (charset == null) ? (new OutputStreamWriter(
1481: new FileOutputStream(other, true)))
1482: : (new OutputStreamWriter(
1483: new FileOutputStream(other,
1484: true), charset)));
1485: // Replace with just "(new FileOutputStream(file), charset)"
1486: // once use defaultCharset from Java 1.5 in charset init.
1487: // above.
1488:
1489: /* Opening in append mode, so it's possible that we will
1490: * be adding superfluous <HTML> and <BODY> tags.
1491: * I think that browsers can handle that */
1492: pwQuery.println((htmlMode ? ("<HTML>" + LS + "<!--")
1493: : "#")
1494: + " "
1495: + (new java.util.Date())
1496: + ". "
1497: + rb.getString(SqltoolRB.OUTPUTFILE_HEADER,
1498: getClass().getName())
1499: + (htmlMode ? (" -->" + LS + LS + "<BODY>")
1500: : LS));
1501: pwQuery.flush();
1502: } catch (Exception e) {
1503: throw new BadSpecial(rb.getString(
1504: SqltoolRB.FILE_WRITEFAIL, other), e);
1505: }
1506:
1507: return;
1508:
1509: case 'i':
1510: enforce1charSpecial(arg1, 'i');
1511: if (other == null) {
1512: throw new BadSpecial(rb
1513: .getString(SqltoolRB.SQLFILE_NAME_DEMAND));
1514: }
1515:
1516: try {
1517: SqlFile sf = new SqlFile(new File(other), false,
1518: userVars);
1519:
1520: sf.recursed = true;
1521:
1522: // Share the possiblyUncommitted state
1523: sf.possiblyUncommitteds = possiblyUncommitteds;
1524: sf.plMode = plMode;
1525:
1526: sf.execute(curConn, continueOnError);
1527: } catch (ContinueException ce) {
1528: throw ce;
1529: } catch (BreakException be) {
1530: String beMessage = be.getMessage();
1531:
1532: // Handle "file" and plain breaks (by doing nothing)
1533: if (beMessage != null && !beMessage.equals("file")) {
1534: throw be;
1535: }
1536: } catch (QuitNow qn) {
1537: throw qn;
1538: } catch (Exception e) {
1539: throw new BadSpecial(rb.getString(
1540: SqltoolRB.SQLFILE_EXECUTE_FAIL, other), e);
1541: }
1542:
1543: return;
1544:
1545: case 'p':
1546: enforce1charSpecial(arg1, 'p');
1547: if (other == null) {
1548: stdprintln(true);
1549: } else {
1550: stdprintln(other, true);
1551: }
1552:
1553: return;
1554:
1555: case 'a':
1556: enforce1charSpecial(arg1, 'a');
1557: if (other != null) {
1558: curConn.setAutoCommit(Boolean.valueOf(other)
1559: .booleanValue());
1560: }
1561:
1562: stdprintln(rb.getString(SqltoolRB.A_SETTING, Boolean
1563: .toString(curConn.getAutoCommit())));
1564:
1565: return;
1566: case '=':
1567: enforce1charSpecial(arg1, '=');
1568: curConn.commit();
1569: possiblyUncommitteds.set(false);
1570: stdprintln(rb.getString(SqltoolRB.COMMITTED));
1571:
1572: return;
1573:
1574: case 'b':
1575: if (arg1.length() == 1) {
1576: if (other != null) {
1577: throw new BadSpecial(rb
1578: .getString(SqltoolRB.SPECIAL_B_MALFORMAT));
1579: }
1580: fetchBinary = true;
1581:
1582: return;
1583: }
1584:
1585: if (arg1.charAt(1) == 'p') {
1586: if (other != null) {
1587: throw new BadSpecial(rb
1588: .getString(SqltoolRB.SPECIAL_B_MALFORMAT));
1589: }
1590: doPrepare = true;
1591:
1592: return;
1593: }
1594:
1595: if ((arg1.charAt(1) != 'd' && arg1.charAt(1) != 'l')
1596: || other == null) {
1597: throw new BadSpecial(rb
1598: .getString(SqltoolRB.SPECIAL_B_MALFORMAT));
1599: }
1600:
1601: File file = new File(other);
1602:
1603: try {
1604: if (arg1.charAt(1) == 'd') {
1605: dump(file);
1606: } else {
1607: binBuffer = SqlFile.loadBinary(file);
1608: stdprintln(rb.getString(
1609: SqltoolRB.BINARY_LOADEDBYTESINTO,
1610: binBuffer.length));
1611: }
1612: } catch (BadSpecial bs) {
1613: throw bs;
1614: } catch (IOException ioe) {
1615: throw new BadSpecial(rb.getString(
1616: SqltoolRB.BINARY_FILEFAIL, other), ioe);
1617: }
1618:
1619: return;
1620:
1621: case '*':
1622: case 'c':
1623: enforce1charSpecial(arg1, '=');
1624: if (other != null) {
1625: // But remember that we have to abort on some I/O errors.
1626: continueOnError = Boolean.valueOf(other).booleanValue();
1627: }
1628:
1629: stdprintln(rb.getString(SqltoolRB.C_SETTING, Boolean
1630: .toString(continueOnError)));
1631:
1632: return;
1633:
1634: case '?':
1635: stdprintln(rb.getString(SqltoolRB.SPECIAL_HELP));
1636:
1637: return;
1638:
1639: case '!':
1640: // N.b. This DOES NOT HANDLE UNIX shell wildcards, since there
1641: // is no UNIX shell involved.
1642: // Doesn't make sense to incur overhead of a shell without
1643: // stdin capability.
1644: // Can't provide stdin to the executed program because
1645: // the forked program could gobble up program input,
1646: // depending on how SqlTool was invoked, nested scripts,
1647: // etc.
1648:
1649: // I'd like to execute the user's default shell if they
1650: // ran "\!" with no argument, but (a) there is no portable
1651: // way to determine the user's default or login shell; and
1652: // (b) shell is useless without stdin ability.
1653: InputStream stream;
1654: byte[] ba = new byte[1024];
1655: String extCommand = ((arg1.length() == 1) ? "" : arg1
1656: .substring(1))
1657: + ((arg1.length() > 1 && other != null) ? " " : "")
1658: + ((other == null) ? "" : other);
1659: if (extCommand.trim().length() < 1)
1660: throw new BadSpecial(rb
1661: .getString(SqltoolRB.BANG_INCOMPLETE));
1662:
1663: try {
1664: Runtime runtime = Runtime.getRuntime();
1665: Process proc = ((wincmdPattern == null) ? runtime
1666: .exec(extCommand) : runtime
1667: .exec(genWinArgs(extCommand)));
1668:
1669: proc.getOutputStream().close();
1670:
1671: int i;
1672:
1673: stream = proc.getInputStream();
1674:
1675: while ((i = stream.read(ba)) > 0) {
1676: stdprint(new String(ba, 0, i));
1677: }
1678:
1679: stream.close();
1680:
1681: stream = proc.getErrorStream();
1682:
1683: while ((i = stream.read(ba)) > 0) {
1684: errprint(new String(ba, 0, i));
1685: }
1686:
1687: stream.close();
1688:
1689: if (proc.waitFor() != 0) {
1690: throw new BadSpecial(rb.getString(
1691: SqltoolRB.BANG_COMMAND_FAIL, extCommand));
1692: }
1693: } catch (BadSpecial bs) {
1694: throw bs;
1695: } catch (Exception e) {
1696: throw new BadSpecial(rb.getString(
1697: SqltoolRB.BANG_COMMAND_FAIL, extCommand), e);
1698: }
1699:
1700: return;
1701:
1702: case '.':
1703: enforce1charSpecial(arg1, '.');
1704: rawMode = RAW_EMPTY;
1705:
1706: if (interactive) {
1707: stdprintln(RAW_LEADIN_MSG);
1708: }
1709:
1710: return;
1711: }
1712:
1713: throw new BadSpecial(rb.getString(SqltoolRB.SPECIAL_UNKNOWN,
1714: Character.toString(arg1.charAt(0))));
1715: }
1716:
1717: private static final char[] nonVarChars = { ' ', '\t', '=', '}',
1718: '\n', '\r' };
1719:
1720: /**
1721: * Returns index specifying 1 past end of a variable name.
1722: *
1723: * @param inString String containing a variable name
1724: * @param startIndex Index within inString where the variable name begins
1725: * @returns Index within inString, 1 past end of the variable name
1726: */
1727: static int pastName(String inString, int startIndex) {
1728: String workString = inString.substring(startIndex);
1729: int e = inString.length(); // Index 1 past end of var name.
1730: int nonVarIndex;
1731:
1732: for (int i = 0; i < nonVarChars.length; i++) {
1733: nonVarIndex = workString.indexOf(nonVarChars[i]);
1734:
1735: if (nonVarIndex > -1 && nonVarIndex < e) {
1736: e = nonVarIndex;
1737: }
1738: }
1739:
1740: return startIndex + e;
1741: }
1742:
1743: /**
1744: * Deference *{} PL variables and ${} System Property variables.
1745: *
1746: * @throws SqlToolError
1747: */
1748: private String dereference(String inString, boolean permitAlias)
1749: throws SqlToolError {
1750: /* TODO: Rewrite using java.util.regex. */
1751: String varName, varValue;
1752: StringBuffer expandBuffer = new StringBuffer(inString);
1753: int b, e; // begin and end of name. end really 1 PAST name
1754: int iterations;
1755:
1756: if (permitAlias && inString.trim().charAt(0) == '/') {
1757: int slashIndex = inString.indexOf('/');
1758:
1759: e = SqlFile.pastName(inString.substring(slashIndex + 1), 0);
1760:
1761: // In this case, e is the exact length of the var name.
1762: if (e < 1) {
1763: throw new SqlToolError(rb
1764: .getString(SqltoolRB.PLALIAS_MALFORMAT));
1765: }
1766:
1767: varName = inString.substring(slashIndex + 1, slashIndex + 1
1768: + e);
1769: varValue = (String) userVars.get(varName);
1770:
1771: if (varValue == null) {
1772: throw new SqlToolError(rb.getString(
1773: SqltoolRB.PLVAR_UNDEFINED, varName));
1774: }
1775:
1776: expandBuffer.replace(slashIndex, slashIndex + 1 + e,
1777: (String) userVars.get(varName));
1778: }
1779:
1780: String s;
1781: boolean permitUnset;
1782: // Permit unset with: ${:varname}
1783: // Prohibit unset with : ${varnam}
1784:
1785: iterations = 0;
1786: while (true) {
1787: s = expandBuffer.toString();
1788: b = s.indexOf("${");
1789:
1790: if (b < 0) {
1791: // No more unexpanded variable uses
1792: break;
1793: }
1794:
1795: e = s.indexOf('}', b + 2);
1796:
1797: if (e == b + 2) {
1798: throw new SqlToolError(rb
1799: .getString(SqltoolRB.SYSPROP_EMPTY));
1800: }
1801:
1802: if (e < 0) {
1803: throw new SqlToolError(rb
1804: .getString(SqltoolRB.SYSPROP_UNTERMINATED));
1805: }
1806:
1807: permitUnset = (s.charAt(b + 2) == ':');
1808:
1809: varName = s.substring(b + (permitUnset ? 3 : 2), e);
1810: if (iterations++ > 10000)
1811: throw new SqlToolError(rb.getString(
1812: SqltoolRB.VAR_INFINITE, varName));
1813:
1814: varValue = System.getProperty(varName);
1815: if (varValue == null) {
1816: if (permitUnset) {
1817: varValue = "";
1818: } else {
1819: throw new SqlToolError(rb.getString(
1820: SqltoolRB.SYSPROP_UNDEFINED, varName));
1821: }
1822: }
1823:
1824: expandBuffer.replace(b, e + 1, varValue);
1825: }
1826:
1827: iterations = 0;
1828: while (true) {
1829: s = expandBuffer.toString();
1830: b = s.indexOf("*{");
1831:
1832: if (b < 0) {
1833: // No more unexpanded variable uses
1834: break;
1835: }
1836:
1837: e = s.indexOf('}', b + 2);
1838:
1839: if (e == b + 2) {
1840: throw new SqlToolError(rb
1841: .getString(SqltoolRB.PLVAR_NAMEEMPTY));
1842: }
1843:
1844: if (e < 0) {
1845: throw new SqlToolError(rb
1846: .getString(SqltoolRB.PLVAR_UNTERMINATED));
1847: }
1848:
1849: permitUnset = (s.charAt(b + 2) == ':');
1850:
1851: varName = s.substring(b + (permitUnset ? 3 : 2), e);
1852: if (iterations++ > 10000)
1853: throw new SqlToolError(rb.getString(
1854: SqltoolRB.VAR_INFINITE, varName));
1855: // TODO: Use a smarter algorithm to handle (or prohibit)
1856: // recursion without this clumsy detection tactic.
1857:
1858: varValue = (String) userVars.get(varName);
1859: if (varValue == null) {
1860: if (permitUnset) {
1861: varValue = "";
1862: } else {
1863: throw new SqlToolError(rb.getString(
1864: SqltoolRB.PLVAR_UNDEFINED, varName));
1865: }
1866: }
1867:
1868: expandBuffer.replace(b, e + 1, varValue);
1869: }
1870:
1871: return expandBuffer.toString();
1872: }
1873:
1874: public boolean plMode = false;
1875:
1876: // PL variable name currently awaiting query output.
1877: private String fetchingVar = null;
1878: private boolean silentFetch = false;
1879: private boolean fetchBinary = false;
1880:
1881: /**
1882: * Process a Process Language Command.
1883: * Nesting not supported yet.
1884: *
1885: * @param inString Complete command, including the leading '\' character.
1886: * @throws BadSpecial special-command-specific errors.
1887: * @throws SqlToolError all other errors, plus BreakException and
1888: * ContinueException.
1889: */
1890: private void processPL(String inString) throws BadSpecial,
1891: SqlToolError {
1892: Matcher m = plPattern.matcher(dereference(inString, false));
1893: if (!m.matches()) {
1894: throw new BadSpecial(rb.getString(SqltoolRB.PL_MALFORMAT));
1895: // I think it's impossible to get here, since the pattern is
1896: // so liberal.
1897: }
1898: if (m.groupCount() < 1 || m.group(1) == null) {
1899: plMode = true;
1900: stdprintln(rb.getString(SqltoolRB.PL_EXPANSIONMODE, "on"));
1901: return;
1902: }
1903:
1904: String[] tokens = m.group(1).split("\\s+");
1905:
1906: if (tokens[0].charAt(0) == '?') {
1907: stdprintln(rb.getString(SqltoolRB.PL_HELP));
1908:
1909: return;
1910: }
1911:
1912: // If user runs any PL command, we turn PL mode on.
1913: plMode = true;
1914:
1915: if (tokens[0].equals("end")) {
1916: throw new BadSpecial(rb.getString(SqltoolRB.END_NOBLOCK));
1917: }
1918:
1919: if (tokens[0].equals("continue")) {
1920: if (tokens.length > 1) {
1921: if (tokens.length == 2
1922: && (tokens[1].equals("foreach") || tokens[1]
1923: .equals("while"))) {
1924: throw new ContinueException(tokens[1]);
1925: }
1926: throw new BadSpecial(rb
1927: .getString(SqltoolRB.CONTINUE_SYNTAX));
1928: }
1929:
1930: throw new ContinueException();
1931: }
1932:
1933: if (tokens[0].equals("break")) {
1934: if (tokens.length > 1) {
1935: if (tokens.length == 2
1936: && (tokens[1].equals("foreach")
1937: || tokens[1].equals("if")
1938: || tokens[1].equals("while") || tokens[1]
1939: .equals("file"))) {
1940: throw new BreakException(tokens[1]);
1941: }
1942: throw new BadSpecial(rb
1943: .getString(SqltoolRB.BREAK_SYNTAX));
1944: }
1945:
1946: throw new BreakException();
1947: }
1948:
1949: if (tokens[0].equals("list") || tokens[0].equals("listvalues")
1950: || tokens[0].equals("listsysprops")) {
1951: boolean sysProps = tokens[0].equals("listsysprops");
1952: String s;
1953: boolean doValues = (tokens[0].equals("listvalues") || sysProps);
1954: // Always list System Property values.
1955: // They are unlikely to be very long, like PL variables may be.
1956:
1957: if (tokens.length == 1) {
1958: stdprint(formatNicely((sysProps ? System
1959: .getProperties() : userVars), doValues));
1960: } else {
1961: if (doValues) {
1962: stdprintln(rb.getString(SqltoolRB.PL_LIST_PARENS));
1963: } else {
1964: stdprintln(rb.getString(SqltoolRB.PL_LIST_LENGTHS));
1965: }
1966:
1967: for (int i = 1; i < tokens.length; i++) {
1968: s = (String) (sysProps ? System.getProperties()
1969: : userVars).get(tokens[i]);
1970: if (s == null)
1971: continue;
1972: stdprintln(" "
1973: + tokens[i]
1974: + ": "
1975: + (doValues ? ("(" + s + ')') : Integer
1976: .toString(s.length())));
1977: }
1978: }
1979:
1980: return;
1981: }
1982:
1983: if (tokens[0].equals("dump") || tokens[0].equals("load")) {
1984: if (tokens.length != 3) {
1985: throw new BadSpecial(rb
1986: .getString(SqltoolRB.DUMPLOAD_MALFORMAT));
1987: }
1988:
1989: String varName = tokens[1];
1990:
1991: if (varName.indexOf(':') > -1) {
1992: throw new BadSpecial(rb
1993: .getString(SqltoolRB.PLVAR_NOCOLON));
1994: }
1995: File file = new File(tokens[2]);
1996:
1997: try {
1998: if (tokens[0].equals("dump")) {
1999: dump(varName, file);
2000: } else {
2001: load(varName, file, charset);
2002: }
2003: } catch (IOException ioe) {
2004: throw new BadSpecial(rb.getString(
2005: SqltoolRB.DUMPLOAD_FAIL, varName, file
2006: .toString()), ioe);
2007: }
2008:
2009: return;
2010: }
2011:
2012: if (tokens[0].equals("prepare")) {
2013: if (tokens.length != 2) {
2014: throw new BadSpecial(rb
2015: .getString(SqltoolRB.PREPARE_MALFORMAT));
2016: }
2017:
2018: if (userVars.get(tokens[1]) == null) {
2019: throw new BadSpecial(rb.getString(
2020: SqltoolRB.PLVAR_UNDEFINED, tokens[1]));
2021: }
2022:
2023: prepareVar = tokens[1];
2024: doPrepare = true;
2025:
2026: return;
2027: }
2028:
2029: if (tokens[0].equals("foreach")) {
2030: Matcher foreachM = foreachPattern.matcher(dereference(
2031: inString, false));
2032: if (!foreachM.matches()) {
2033: throw new BadSpecial(rb
2034: .getString(SqltoolRB.FOREACH_MALFORMAT));
2035: }
2036: if (foreachM.groupCount() != 2) {
2037: throw new RuntimeException(
2038: "foreach pattern matched, but captured "
2039: + foreachM.groupCount() + " groups");
2040: }
2041:
2042: String varName = foreachM.group(1);
2043: if (varName.indexOf(':') > -1) {
2044: throw new BadSpecial(rb
2045: .getString(SqltoolRB.PLVAR_NOCOLON));
2046: }
2047: String[] values = foreachM.group(2).split("\\s+");
2048: File tmpFile = null;
2049: String varVal;
2050:
2051: try {
2052: tmpFile = plBlockFile("foreach");
2053: } catch (IOException ioe) {
2054: throw new BadSpecial(rb
2055: .getString(SqltoolRB.PL_TEMPFILE_FAIL), ioe);
2056: }
2057:
2058: String origval = (String) userVars.get(varName);
2059:
2060: try {
2061: SqlFile sf;
2062:
2063: for (int i = 0; i < values.length; i++) {
2064: try {
2065: varVal = values[i];
2066:
2067: userVars.put(varName, varVal);
2068: updateUserSettings();
2069:
2070: sf = new SqlFile(tmpFile, false, userVars);
2071: sf.plMode = true;
2072: sf.recursed = true;
2073:
2074: // Share the possiblyUncommitted state
2075: sf.possiblyUncommitteds = possiblyUncommitteds;
2076:
2077: sf.execute(curConn, continueOnError);
2078: } catch (ContinueException ce) {
2079: String ceMessage = ce.getMessage();
2080:
2081: if (ceMessage != null
2082: && !ceMessage.equals("foreach")) {
2083: throw ce;
2084: }
2085: }
2086: }
2087: } catch (BreakException be) {
2088: String beMessage = be.getMessage();
2089:
2090: // Handle "foreach" and plain breaks (by doing nothing)
2091: if (beMessage != null && !beMessage.equals("foreach")) {
2092: throw be;
2093: }
2094: } catch (QuitNow qn) {
2095: throw qn;
2096: } catch (Exception e) {
2097: throw new BadSpecial(rb
2098: .getString(SqltoolRB.PL_BLOCK_FAIL), e);
2099: }
2100:
2101: if (origval == null) {
2102: userVars.remove(varName);
2103: updateUserSettings();
2104: } else {
2105: userVars.put(varName, origval);
2106: }
2107:
2108: if (tmpFile != null && !tmpFile.delete()) {
2109: throw new BadSpecial(rb.getString(
2110: SqltoolRB.TEMPFILE_REMOVAL_FAIL, tmpFile
2111: .toString()));
2112: }
2113:
2114: return;
2115: }
2116:
2117: if (tokens[0].equals("if") || tokens[0].equals("while")) {
2118: Matcher ifwhileM = ifwhilePattern.matcher(dereference(
2119: inString, false));
2120: if (!ifwhileM.matches()) {
2121: throw new BadSpecial(rb
2122: .getString(SqltoolRB.IFWHILE_MALFORMAT));
2123: }
2124: if (ifwhileM.groupCount() != 1) {
2125: throw new RuntimeException(
2126: "if/while pattern matched, but captured "
2127: + ifwhileM.groupCount() + " groups");
2128: }
2129:
2130: String[] values = ifwhileM.group(1).replaceAll(
2131: "!([a-zA-Z0-9*])", "! $1").replaceAll(
2132: "([a-zA-Z0-9*])!", "$1 !").split("\\s+");
2133: File tmpFile = null;
2134:
2135: if (tokens[0].equals("if")) {
2136: try {
2137: tmpFile = plBlockFile("if");
2138: } catch (IOException ioe) {
2139: throw new BadSpecial(rb
2140: .getString(SqltoolRB.PL_BLOCK_FAIL), ioe);
2141: }
2142:
2143: try {
2144: if (eval(values)) {
2145: SqlFile sf = new SqlFile(tmpFile, false,
2146: userVars);
2147:
2148: sf.plMode = true;
2149: sf.recursed = true;
2150:
2151: // Share the possiblyUncommitted state
2152: sf.possiblyUncommitteds = possiblyUncommitteds;
2153:
2154: sf.execute(curConn, continueOnError);
2155: }
2156: } catch (BreakException be) {
2157: String beMessage = be.getMessage();
2158:
2159: // Handle "if" and plain breaks (by doing nothing)
2160: if (beMessage == null || !beMessage.equals("if")) {
2161: throw be;
2162: }
2163: } catch (ContinueException ce) {
2164: throw ce;
2165: } catch (QuitNow qn) {
2166: throw qn;
2167: } catch (BadSpecial bs) {
2168: bs.appendMessage(rb
2169: .getString(SqltoolRB.IF_MALFORMAT));
2170: throw bs;
2171: } catch (Exception e) {
2172: throw new BadSpecial(rb
2173: .getString(SqltoolRB.PL_BLOCK_FAIL), e);
2174: }
2175: } else if (tokens[0].equals("while")) {
2176: try {
2177: tmpFile = plBlockFile("while");
2178: } catch (IOException ioe) {
2179: throw new BadSpecial(rb
2180: .getString(SqltoolRB.PL_TEMPFILE_FAIL), ioe);
2181: }
2182:
2183: try {
2184: SqlFile sf;
2185:
2186: while (eval(values)) {
2187: try {
2188: sf = new SqlFile(tmpFile, false, userVars);
2189: sf.recursed = true;
2190:
2191: // Share the possiblyUncommitted state
2192: sf.possiblyUncommitteds = possiblyUncommitteds;
2193: sf.plMode = true;
2194:
2195: sf.execute(curConn, continueOnError);
2196: } catch (ContinueException ce) {
2197: String ceMessage = ce.getMessage();
2198:
2199: if (ceMessage != null
2200: && !ceMessage.equals("while")) {
2201: throw ce;
2202: }
2203: }
2204: }
2205: } catch (BreakException be) {
2206: String beMessage = be.getMessage();
2207:
2208: // Handle "while" and plain breaks (by doing nothing)
2209: if (beMessage != null && !beMessage.equals("while")) {
2210: throw be;
2211: }
2212: } catch (QuitNow qn) {
2213: throw qn;
2214: } catch (BadSpecial bs) {
2215: bs.appendMessage(rb
2216: .getString(SqltoolRB.WHILE_MALFORMAT));
2217: throw bs;
2218: } catch (Exception e) {
2219: throw new BadSpecial(rb
2220: .getString(SqltoolRB.PL_BLOCK_FAIL), e);
2221: }
2222: } else {
2223: // Assertion
2224: throw new RuntimeException(rb.getString(
2225: SqltoolRB.PL_UNKNOWN, tokens[0]));
2226: }
2227:
2228: if (tmpFile != null && !tmpFile.delete()) {
2229: throw new BadSpecial(rb.getString(
2230: SqltoolRB.TEMPFILE_REMOVAL_FAIL, tmpFile
2231: .toString()));
2232: }
2233:
2234: return;
2235: }
2236:
2237: m = varsetPattern.matcher(dereference(inString, false));
2238: if (!m.matches()) {
2239: throw new BadSpecial(rb.getString(SqltoolRB.PL_UNKNOWN,
2240: tokens[0]));
2241: }
2242: if (m.groupCount() < 2 || m.groupCount() > 3) {
2243: // Assertion
2244: throw new RuntimeException(
2245: "varset pattern matched but captured "
2246: + m.groupCount() + " groups");
2247: }
2248:
2249: String varName = m.group(1);
2250:
2251: if (varName.indexOf(':') > -1) {
2252: throw new BadSpecial(rb.getString(SqltoolRB.PLVAR_NOCOLON));
2253: }
2254:
2255: switch (m.group(2).charAt(0)) {
2256: case '_':
2257: silentFetch = true;
2258: case '~':
2259: if (m.groupCount() > 2 && m.group(3) != null) {
2260: throw new BadSpecial(rb.getString(
2261: SqltoolRB.PLVAR_TILDEDASH_NOMOREARGS, m
2262: .group(3)));
2263: }
2264:
2265: userVars.remove(varName);
2266: updateUserSettings();
2267:
2268: fetchingVar = varName;
2269:
2270: return;
2271:
2272: case '=':
2273: if (fetchingVar != null && fetchingVar.equals(varName)) {
2274: fetchingVar = null;
2275: }
2276:
2277: if (m.groupCount() > 2 && m.group(3) != null) {
2278: userVars.put(varName, m.group(3));
2279: } else {
2280: userVars.remove(varName);
2281: }
2282: updateUserSettings();
2283:
2284: return;
2285: }
2286:
2287: throw new BadSpecial(rb.getString(SqltoolRB.PL_UNKNOWN,
2288: tokens[0]));
2289: // I think this would already be caught in the setvar block above.
2290: }
2291:
2292: /*
2293: * Read a PL block into a new temp file.
2294: *
2295: * WARNING!!! foreach blocks are not yet smart about comments
2296: * and strings. We just look for a line beginning with a PL "end"
2297: * command without worrying about comments or quotes (for now).
2298: *
2299: * WARNING!!! This is very rudimentary.
2300: * Users give up all editing and feedback capabilities while
2301: * in the foreach loop.
2302: * A better solution would be to pass current input stream to a
2303: * new SqlFile.execute() with a mode whereby commands are written
2304: * to a separate history but not executed.
2305: *
2306: * @throws IOException
2307: * @throws SqlToolError
2308: */
2309: private File plBlockFile(String seeking) throws IOException,
2310: SqlToolError {
2311: String s;
2312:
2313: // Have already read the if/while/foreach statement, so we are already
2314: // at nest level 1. When we reach nestlevel 1 (read 1 net "end"
2315: // statement), we're at level 0 and return.
2316: int nestlevel = 1;
2317: String curPlCommand;
2318:
2319: if (seeking == null
2320: || ((!seeking.equals("foreach"))
2321: && (!seeking.equals("if")) && (!seeking
2322: .equals("while")))) {
2323: throw new RuntimeException(
2324: "Assertion failed. Unsupported PL block type: "
2325: + seeking);
2326: }
2327:
2328: File tmpFile = File.createTempFile("sqltool-", ".sql");
2329: PrintWriter pw = new PrintWriter(
2330: (charset == null) ? (new OutputStreamWriter(
2331: new FileOutputStream(tmpFile)))
2332: : (new OutputStreamWriter(new FileOutputStream(
2333: tmpFile), charset)));
2334: // Replace with just "(new FileOutputStream(file), charset)"
2335: // once use defaultCharset from Java 1.5 in charset init. above.
2336:
2337: try {
2338: pw.println("/* " + (new java.util.Date()) + ". "
2339: + getClass().getName() + " PL block. */");
2340: pw.println();
2341: Matcher m;
2342:
2343: while (true) {
2344: s = br.readLine();
2345:
2346: if (s == null) {
2347: s = rb.getString(SqltoolRB.PL_BLOCK_UNTERMINATED,
2348: seeking);
2349: errprintln(s);
2350: throw new SqlToolError(s);
2351: }
2352:
2353: curLinenum++;
2354:
2355: m = plPattern.matcher(s);
2356: if (m.matches() && m.groupCount() > 0
2357: && m.group(1) != null) {
2358: String[] tokens = m.group(1).split("\\s+");
2359: curPlCommand = tokens[0];
2360:
2361: // PL COMMAND of some sort.
2362: if (curPlCommand.equals(seeking)) {
2363: nestlevel++;
2364: } else if (curPlCommand.equals("end")) {
2365: if (tokens.length < 2) {
2366: s = rb.getString(SqltoolRB.END_SYNTAX, "1");
2367: errprintln(s);
2368: throw new SqlToolError(s);
2369: }
2370:
2371: String inType = tokens[1];
2372:
2373: if (inType.equals(seeking)) {
2374: nestlevel--;
2375:
2376: if (nestlevel < 1) {
2377: break;
2378: }
2379: }
2380:
2381: if ((!inType.equals("foreach"))
2382: && (!inType.equals("if"))
2383: && (!inType.equals("while"))) {
2384: s = rb.getString(SqltoolRB.END_SYNTAX, "2");
2385: errprintln(s);
2386: throw new SqlToolError(s);
2387: }
2388: }
2389: }
2390:
2391: pw.println(s);
2392: }
2393:
2394: pw.flush();
2395: } finally {
2396: pw.close();
2397: }
2398:
2399: return tmpFile;
2400: }
2401:
2402: /**
2403: * Wrapper methods so don't need to call x(..., false) in most cases.
2404: */
2405: /* Unused. Enable when/if need.
2406: private void stdprintln() {
2407: stdprintln(false);
2408: }
2409: */
2410:
2411: private void stdprint(String s) {
2412: stdprint(s, false);
2413: }
2414:
2415: private void stdprintln(String s) {
2416: stdprintln(s, false);
2417: }
2418:
2419: /**
2420: * Encapsulates normal output.
2421: *
2422: * Conditionally HTML-ifies output.
2423: */
2424: private void stdprintln(boolean queryOutput) {
2425: if (htmlMode) {
2426: psStd.println("<BR>");
2427: } else {
2428: psStd.println();
2429: }
2430:
2431: if (queryOutput && pwQuery != null) {
2432: if (htmlMode) {
2433: pwQuery.println("<BR>");
2434: } else {
2435: pwQuery.println();
2436: }
2437:
2438: pwQuery.flush();
2439: }
2440: }
2441:
2442: /**
2443: * Encapsulates error output.
2444: *
2445: * Conditionally HTML-ifies error output.
2446: */
2447: private void errprint(String s) {
2448: psErr
2449: .print(htmlMode ? ("<DIV style='color:white; background: red; "
2450: + "font-weight: bold'>" + s + "</DIV>")
2451: : s);
2452: }
2453:
2454: /**
2455: * Encapsulates error output.
2456: *
2457: * Conditionally HTML-ifies error output.
2458: */
2459: private void errprintln(String s) {
2460: psErr
2461: .println(htmlMode ? ("<DIV style='color:white; background: red; "
2462: + "font-weight: bold'>" + s + "</DIV>")
2463: : s);
2464: }
2465:
2466: /**
2467: * Encapsulates normal output.
2468: *
2469: * Conditionally HTML-ifies output.
2470: */
2471: private void stdprint(String s, boolean queryOutput) {
2472: psStd.print(htmlMode ? ("<P>" + s + "</P>") : s);
2473:
2474: if (queryOutput && pwQuery != null) {
2475: pwQuery.print(htmlMode ? ("<P>" + s + "</P>") : s);
2476: pwQuery.flush();
2477: }
2478: }
2479:
2480: /**
2481: * Encapsulates normal output.
2482: *
2483: * Conditionally HTML-ifies output.
2484: */
2485: private void stdprintln(String s, boolean queryOutput) {
2486: psStd.println(htmlMode ? ("<P>" + s + "</P>") : s);
2487:
2488: if (queryOutput && pwQuery != null) {
2489: pwQuery.println(htmlMode ? ("<P>" + s + "</P>") : s);
2490: pwQuery.flush();
2491: }
2492: }
2493:
2494: // Just because users may be used to seeing "[null]" in normal
2495: // SqlFile output, we use the same default value for null in DSV
2496: // files, but this DSV null representation can be changed to anything.
2497: private static final String DEFAULT_NULL_REP = "[null]";
2498: private static final String DEFAULT_ROW_DELIM = LS;
2499: private static final String DEFAULT_COL_DELIM = "|";
2500: private static final String DEFAULT_SKIP_PREFIX = "#";
2501: private static final int DEFAULT_ELEMENT = 0, HSQLDB_ELEMENT = 1,
2502: ORACLE_ELEMENT = 2;
2503:
2504: // These do not specify order listed, just inclusion.
2505: private static final int[] listMDSchemaCols = { 1 };
2506: private static final int[] listMDIndexCols = { 2, 6, 3, 9, 4, 10,
2507: 11 };
2508:
2509: /** Column numbering starting at 1. */
2510: private static final int[][] listMDTableCols = { { 2, 3 }, // Default
2511: { 2, 3 }, // HSQLDB
2512: { 2, 3 }, // Oracle
2513: };
2514:
2515: /**
2516: * SYS and SYSTEM are the only base system accounts in Oracle, however,
2517: * from an empirical perspective, all of these other accounts are
2518: * system accounts because <UL>
2519: * <LI> they are hidden from the casual user
2520: * <LI> they are created by the installer at installation-time
2521: * <LI> they are used automatically by the Oracle engine when the
2522: * specific Oracle sub-product is used
2523: * <LI> the accounts should not be <I>messed with</I> by database users
2524: * <LI> the accounts should certainly not be used if the specific
2525: * Oracle sub-product is going to be used.
2526: * </UL>
2527: *
2528: * General advice: If you aren't going to use an Oracle sub-product,
2529: * then <B>don't install it!</B>
2530: * Don't blindly accept default when running OUI.
2531: *
2532: * If users also see accounts that they didn't create with names like
2533: * SCOTT, ADAMS, JONES, CLARK, BLAKE, OE, PM, SH, QS, QS_*, these
2534: * contain sample data and the schemas can safely be removed.
2535: */
2536: private static final String[] oracleSysSchemas = { "SYS", "SYSTEM",
2537: "OUTLN", "DBSNMP", "OUTLN", "MDSYS", "ORDSYS",
2538: "ORDPLUGINS", "CTXSYS", "DSSYS", "PERFSTAT", "WKPROXY",
2539: "WKSYS", "WMSYS", "XDB", "ANONYMOUS", "ODM", "ODM_MTR",
2540: "OLAPSYS", "TRACESVR", "REPADMIN" };
2541:
2542: /**
2543: * Lists available database tables.
2544: *
2545: * When a filter is given, we assume that there are no lower-case
2546: * characters in the object names (which would require "quotes" when
2547: * creating them).
2548: *
2549: * @throws BadSpecial usually wrap a cause (which cause is a
2550: * SQLException in some cases).
2551: * @throws SqlToolError passed through from other methods in this class.
2552: */
2553: private void listTables(char c, String inFilter) throws BadSpecial,
2554: SqlToolError {
2555: String schema = null;
2556: int[] listSet = null;
2557: String[] types = null;
2558:
2559: /** For workaround for \T for Oracle */
2560: String[] additionalSchemas = null;
2561:
2562: /** This is for specific non-getTable() queries */
2563: Statement statement = null;
2564: ResultSet rs = null;
2565: String narrower = "";
2566: /*
2567: * Doing case-sensitive filters now, for greater portability.
2568: String filter = ((inFilter == null)
2569: ? null : inFilter.toUpperCase());
2570: */
2571: String filter = inFilter;
2572:
2573: try {
2574: DatabaseMetaData md = curConn.getMetaData();
2575: String dbProductName = md.getDatabaseProductName();
2576:
2577: //System.err.println("DB NAME = (" + dbProductName + ')');
2578: // Database-specific table filtering.
2579:
2580: /* 3 Types of actions:
2581: * 1) Special handling. Return from the "case" block directly.
2582: * 2) Execute a specific query. Set statement in the "case".
2583: * 3) Otherwise, set filter info for dbmd.getTable() in the
2584: * "case".
2585: */
2586: types = new String[1];
2587:
2588: switch (c) {
2589: case '*':
2590: types = null;
2591: break;
2592:
2593: case 'S':
2594: if (dbProductName.indexOf("Oracle") > -1) {
2595: errprintln(rb.getString(SqltoolRB.VENDOR_ORACLE_DS));
2596:
2597: types[0] = "TABLE";
2598: schema = "SYS";
2599: additionalSchemas = oracleSysSchemas;
2600: } else {
2601: types[0] = "SYSTEM TABLE";
2602: }
2603: break;
2604:
2605: case 's':
2606: if (dbProductName.indexOf("HSQL") > -1) {
2607: // HSQLDB does not consider Sequences as "tables",
2608: // hence we do not list them in
2609: // DatabaseMetaData.getTables().
2610: if (filter != null
2611: && filter.charAt(filter.length() - 1) == '.') {
2612: narrower = "\nWHERE sequence_schema = '"
2613: + filter.substring(0,
2614: filter.length() - 1) + "'";
2615: filter = null;
2616: }
2617:
2618: statement = curConn.createStatement();
2619:
2620: statement
2621: .execute("SELECT sequence_schema, sequence_name FROM "
2622: + "information_schema.system_sequences"
2623: + narrower);
2624: } else {
2625: types[0] = "SEQUENCE";
2626: }
2627: break;
2628:
2629: case 'r':
2630: if (dbProductName.indexOf("HSQL") > -1) {
2631: statement = curConn.createStatement();
2632:
2633: statement
2634: .execute("SELECT authorization_name FROM "
2635: + "information_schema.system_authorizations\n"
2636: + "WHERE authorization_type = 'ROLE'\n"
2637: + "ORDER BY authorization_name");
2638: } else if (dbProductName
2639: .indexOf("Adaptive Server Enterprise") > -1) {
2640: // This is the basic Sybase server. Sybase also has
2641: // their "Anywhere", ASA (for embedded), and replication
2642: // databases, but I don't know the Metadata strings for
2643: // those.
2644: statement = curConn.createStatement();
2645:
2646: statement
2647: .execute("SELECT name FROM syssrvroles ORDER BY name");
2648: } else if (dbProductName.indexOf("Apache Derby") > -1) {
2649: throw new BadSpecial(rb
2650: .getString(SqltoolRB.VENDOR_DERBY_DR));
2651: } else {
2652: throw new BadSpecial(rb.getString(
2653: SqltoolRB.VENDOR_NOSUP_D, "r"));
2654: }
2655: break;
2656:
2657: case 'u':
2658: if (dbProductName.indexOf("HSQL") > -1) {
2659: statement = curConn.createStatement();
2660:
2661: statement.execute("SELECT user, admin FROM "
2662: + "information_schema.system_users\n"
2663: + "ORDER BY user");
2664: } else if (dbProductName.indexOf("Oracle") > -1) {
2665: statement = curConn.createStatement();
2666:
2667: statement
2668: .execute("SELECT username, created FROM all_users "
2669: + "ORDER BY username");
2670: } else if (dbProductName.indexOf("PostgreSQL") > -1) {
2671: statement = curConn.createStatement();
2672:
2673: statement
2674: .execute("SELECT usename, usesuper FROM pg_catalog.pg_user "
2675: + "ORDER BY usename");
2676: } else if (dbProductName
2677: .indexOf("Adaptive Server Enterprise") > -1) {
2678: // This is the basic Sybase server. Sybase also has
2679: // their "Anywhere", ASA (for embedded), and replication
2680: // databases, but I don't know the Metadata strings for
2681: // those.
2682: statement = curConn.createStatement();
2683:
2684: statement
2685: .execute("SELECT name, accdate, fullname FROM syslogins "
2686: + "ORDER BY name");
2687: } else if (dbProductName.indexOf("Apache Derby") > -1) {
2688: throw new BadSpecial(rb
2689: .getString(SqltoolRB.VENDOR_DERBY_DU));
2690: } else {
2691: throw new BadSpecial(rb.getString(
2692: SqltoolRB.VENDOR_NOSUP_D, "u"));
2693: }
2694: break;
2695:
2696: case 'a':
2697: if (dbProductName.indexOf("HSQL") > -1) {
2698: // HSQLDB Aliases are not the same things as the
2699: // aliases listed in DatabaseMetaData.getTables().
2700: if (filter != null
2701: && filter.charAt(filter.length() - 1) == '.') {
2702: narrower = "\nWHERE alias_schem = '"
2703: + filter.substring(0,
2704: filter.length() - 1) + "'";
2705: filter = null;
2706: }
2707:
2708: statement = curConn.createStatement();
2709:
2710: statement.execute("SELECT alias_schem, alias FROM "
2711: + "information_schema.system_aliases"
2712: + narrower);
2713: } else {
2714: types[0] = "ALIAS";
2715: }
2716: break;
2717:
2718: case 't':
2719: excludeSysSchemas = (dbProductName.indexOf("Oracle") > -1);
2720: types[0] = "TABLE";
2721: break;
2722:
2723: case 'v':
2724: types[0] = "VIEW";
2725: break;
2726:
2727: case 'n':
2728: rs = md.getSchemas();
2729:
2730: if (rs == null) {
2731: throw new BadSpecial(
2732: "Failed to get metadata from database");
2733: }
2734:
2735: displayResultSet(null, rs, listMDSchemaCols, filter);
2736:
2737: return;
2738:
2739: case 'i':
2740:
2741: // Some databases require to specify table, some don't.
2742: /*
2743: if (filter == null) {
2744: throw new BadSpecial("You must specify the index's "
2745: + "table as argument to \\di");
2746: }
2747: */
2748: String table = null;
2749:
2750: if (filter != null) {
2751: int dotat = filter.indexOf('.');
2752:
2753: schema = ((dotat > 0) ? filter.substring(0, dotat)
2754: : null);
2755:
2756: if (dotat < filter.length() - 1) {
2757: // Not a schema-only specifier
2758: table = ((dotat > 0) ? filter
2759: .substring(dotat + 1) : filter);
2760: }
2761:
2762: filter = null;
2763: }
2764:
2765: // N.b. Oracle incorrectly reports the INDEX SCHEMA as
2766: // the TABLE SCHEMA. The Metadata structure seems to
2767: // be designed with the assumption that the INDEX schema
2768: // will be the same as the TABLE schema.
2769: rs = md.getIndexInfo(null, schema, table, false, true);
2770:
2771: if (rs == null) {
2772: throw new BadSpecial(
2773: "Failed to get metadata from database");
2774: }
2775:
2776: displayResultSet(null, rs, listMDIndexCols, null);
2777:
2778: return;
2779:
2780: default:
2781: throw new BadSpecial(rb.getString(
2782: SqltoolRB.SPECIAL_D_UNKNOWN, Character
2783: .toString(c))
2784: + LS + D_OPTIONS_TEXT);
2785: }
2786:
2787: if (statement == null) {
2788: if (dbProductName.indexOf("HSQL") > -1) {
2789: listSet = listMDTableCols[HSQLDB_ELEMENT];
2790: } else if (dbProductName.indexOf("Oracle") > -1) {
2791: listSet = listMDTableCols[ORACLE_ELEMENT];
2792: } else {
2793: listSet = listMDTableCols[DEFAULT_ELEMENT];
2794: }
2795:
2796: if (schema == null && filter != null
2797: && filter.charAt(filter.length() - 1) == '.') {
2798: schema = filter.substring(0, filter.length() - 1);
2799: filter = null;
2800: }
2801: }
2802:
2803: rs = ((statement == null) ? md.getTables(null, schema,
2804: null, types) : statement.getResultSet());
2805:
2806: if (rs == null) {
2807: throw new BadSpecial(rb
2808: .getString(SqltoolRB.METADATA_FETCH_FAIL));
2809: }
2810:
2811: displayResultSet(null, rs, listSet, filter);
2812:
2813: if (additionalSchemas != null) {
2814: for (int i = 1; i < additionalSchemas.length; i++) {
2815: /*
2816: * Inefficient, but we have to do each successful query
2817: * twice in order to prevent calling displayResultSet
2818: * for empty/non-existent schemas
2819: */
2820: rs = md.getTables(null, additionalSchemas[i], null,
2821: types);
2822:
2823: if (rs == null) {
2824: throw new BadSpecial(rb.getString(
2825: SqltoolRB.METADATA_FETCH_FAILFOR,
2826: additionalSchemas[i]));
2827: }
2828:
2829: if (!rs.next()) {
2830: continue;
2831: }
2832:
2833: displayResultSet(null, md.getTables(null,
2834: additionalSchemas[i], null, types),
2835: listSet, filter);
2836: }
2837: }
2838: } catch (SQLException se) {
2839: throw new BadSpecial(rb
2840: .getString(SqltoolRB.METADATA_FETCH_FAIL), se);
2841: } catch (NullPointerException npe) {
2842: throw new BadSpecial(rb
2843: .getString(SqltoolRB.METADATA_FETCH_FAIL), npe);
2844: } finally {
2845: excludeSysSchemas = false;
2846:
2847: if (rs != null) {
2848: rs = null;
2849: }
2850:
2851: if (statement != null) {
2852: try {
2853: statement.close();
2854: } catch (Exception e) {
2855: }
2856:
2857: statement = null;
2858: }
2859: }
2860: }
2861:
2862: private boolean excludeSysSchemas = false;
2863:
2864: /**
2865: * Process the contents of Edit Buffer as an SQL Statement
2866: *
2867: * @throws SQLException thrown by JDBC driver.
2868: * @throws SqlToolError all other errors.
2869: */
2870: private void processSQL() throws SQLException, SqlToolError {
2871: // Really don't know whether to take the network latency hit here
2872: // in order to check autoCommit in order to set
2873: // possiblyUncommitteds more accurately.
2874: // I'm going with "NO" for now, since autoCommit will usually be off.
2875: // If we do ever check autocommit, we have to keep track of the
2876: // autocommit state when every SQL statement is run, since I may
2877: // be able to have uncommitted DML, turn autocommit on, then run
2878: // other DDL with autocommit on. As a result, I could be running
2879: // SQL commands with autotommit on but still have uncommitted mods.
2880: // (For all I know, the database could commit or rollback whenever
2881: // the autocommit option is changed, and that behavior could be
2882: // DB-specific).
2883: lastSqlStatement = (plMode ? dereference(buffer, true) : buffer);
2884: Statement statement = null;
2885:
2886: if (doPrepare) {
2887: if (lastSqlStatement.indexOf('?') < 1) {
2888: lastSqlStatement = null;
2889: throw new SqlToolError(rb
2890: .getString(SqltoolRB.PREPARE_DEMANDQM));
2891: }
2892:
2893: doPrepare = false;
2894:
2895: PreparedStatement ps = curConn
2896: .prepareStatement(lastSqlStatement);
2897:
2898: if (prepareVar == null) {
2899: if (binBuffer == null) {
2900: lastSqlStatement = null;
2901: throw new SqlToolError(rb
2902: .getString(SqltoolRB.BINBUFFER_EMPTY));
2903: }
2904:
2905: ps.setBytes(1, binBuffer);
2906: } else {
2907: String val = (String) userVars.get(prepareVar);
2908:
2909: if (val == null) {
2910: lastSqlStatement = null;
2911: throw new SqlToolError(rb.getString(
2912: SqltoolRB.PLVAR_UNDEFINED, prepareVar));
2913: }
2914:
2915: prepareVar = null;
2916:
2917: ps.setString(1, val);
2918: }
2919:
2920: ps.executeUpdate();
2921:
2922: statement = ps;
2923: } else {
2924: statement = curConn.createStatement();
2925:
2926: statement.execute(lastSqlStatement);
2927: }
2928:
2929: possiblyUncommitteds.set(true);
2930:
2931: try {
2932: displayResultSet(statement, statement.getResultSet(), null,
2933: null);
2934: } finally {
2935: try {
2936: statement.close();
2937: } catch (Exception e) {
2938: }
2939: }
2940: lastSqlStatement = null;
2941: }
2942:
2943: /**
2944: * Display the given result set for user.
2945: * The last 3 params are to narrow down records and columns where
2946: * that can not be done with a where clause (like in metadata queries).
2947: *
2948: * @param statement The SQL Statement that the result set is for.
2949: * (This is so we can get the statement's update count.
2950: * Can be null for non-update queries.)
2951: * @param r The ResultSet to display.
2952: * @param incCols Optional list of which columns to include (i.e., if
2953: * given, then other columns will be skipped).
2954: * @param incFilter Optional case-insensitive substring.
2955: * Rows are skipped which to not contain this substring.
2956: * @throws SQLException thrown by JDBC driver.
2957: * @throws SqlToolError all other errors.
2958: */
2959: private void displayResultSet(Statement statement, ResultSet r,
2960: int[] incCols, String filter) throws SQLException,
2961: SqlToolError {
2962: java.sql.Timestamp ts;
2963: int dotAt;
2964: int updateCount = (statement == null) ? -1 : statement
2965: .getUpdateCount();
2966: boolean silent = silentFetch;
2967: boolean binary = fetchBinary;
2968:
2969: silentFetch = false;
2970: fetchBinary = false;
2971:
2972: if (excludeSysSchemas) {
2973: stdprintln(rb.getString(SqltoolRB.VENDOR_NOSUP_SYSSCHEMAS));
2974: }
2975:
2976: switch (updateCount) {
2977: case -1:
2978: if (r == null) {
2979: stdprintln(rb.getString(SqltoolRB.NORESULT), true);
2980:
2981: break;
2982: }
2983:
2984: ResultSetMetaData m = r.getMetaData();
2985: int cols = m.getColumnCount();
2986: int incCount = (incCols == null) ? cols : incCols.length;
2987: String val;
2988: List rows = new ArrayList();
2989: String[] headerArray = null;
2990: String[] fieldArray;
2991: int[] maxWidth = new int[incCount];
2992: int insi;
2993: boolean skip;
2994:
2995: // STEP 1: GATHER DATA
2996: if (!htmlMode) {
2997: for (int i = 0; i < maxWidth.length; i++) {
2998: maxWidth[i] = 0;
2999: }
3000: }
3001:
3002: boolean[] rightJust = new boolean[incCount];
3003: int[] dataType = new int[incCount];
3004: boolean[] autonulls = new boolean[incCount];
3005:
3006: insi = -1;
3007: headerArray = new String[incCount];
3008:
3009: for (int i = 1; i <= cols; i++) {
3010: if (incCols != null) {
3011: skip = true;
3012:
3013: for (int j = 0; j < incCols.length; j++) {
3014: if (i == incCols[j]) {
3015: skip = false;
3016: }
3017: }
3018:
3019: if (skip) {
3020: continue;
3021: }
3022: }
3023:
3024: headerArray[++insi] = m.getColumnLabel(i);
3025: dataType[insi] = m.getColumnType(i);
3026: rightJust[insi] = false;
3027: autonulls[insi] = true;
3028:
3029: switch (dataType[insi]) {
3030: case java.sql.Types.BIGINT:
3031: case java.sql.Types.BIT:
3032: case java.sql.Types.DECIMAL:
3033: case java.sql.Types.DOUBLE:
3034: case java.sql.Types.FLOAT:
3035: case java.sql.Types.INTEGER:
3036: case java.sql.Types.NUMERIC:
3037: case java.sql.Types.REAL:
3038: case java.sql.Types.SMALLINT:
3039: case java.sql.Types.TINYINT:
3040: rightJust[insi] = true;
3041: break;
3042:
3043: case java.sql.Types.VARBINARY:
3044: case java.sql.Types.VARCHAR:
3045: case java.sql.Types.ARRAY:
3046: // Guessing at how to handle ARRAY.
3047: case java.sql.Types.BLOB:
3048: case java.sql.Types.CLOB:
3049: case java.sql.Types.LONGVARBINARY:
3050: case java.sql.Types.LONGVARCHAR:
3051: autonulls[insi] = false;
3052: break;
3053: }
3054:
3055: if (htmlMode) {
3056: continue;
3057: }
3058:
3059: if (headerArray[insi].length() > maxWidth[insi]) {
3060: maxWidth[insi] = headerArray[insi].length();
3061: }
3062: }
3063:
3064: boolean filteredOut;
3065:
3066: while (r.next()) {
3067: fieldArray = new String[incCount];
3068: insi = -1;
3069: filteredOut = filter != null;
3070:
3071: for (int i = 1; i <= cols; i++) {
3072: // This is the only case where we can save a data
3073: // read by recognizing we don't need this datum early.
3074: if (incCols != null) {
3075: skip = true;
3076:
3077: for (int j = 0; j < incCols.length; j++) {
3078: if (i == incCols[j]) {
3079: skip = false;
3080: }
3081: }
3082:
3083: if (skip) {
3084: continue;
3085: }
3086: }
3087:
3088: // This row may still be ditched, but it is now
3089: // certain that we need to increment the fieldArray
3090: // index.
3091: ++insi;
3092:
3093: if (!SqlFile.canDisplayType(dataType[insi])) {
3094: binary = true;
3095: }
3096:
3097: val = null;
3098:
3099: if (!binary) {
3100: /*
3101: * The special formatting for all time-related
3102: * fields is because the most popular current
3103: * databases are extremely inconsistent about
3104: * what resolution is returned for the same types.
3105: * In my experience so far, Dates MAY have
3106: * resolution down to second, but only TIMESTAMPs
3107: * support sub-second res. (and always can).
3108: * On top of that there is no consistency across
3109: * getObject().toString(). Oracle doesn't even
3110: * implement it for their custom TIMESTAMP type.
3111: */
3112: switch (dataType[insi]) {
3113: case java.sql.Types.TIMESTAMP:
3114: case java.sql.Types.DATE:
3115: case java.sql.Types.TIME:
3116: ts = r.getTimestamp(i);
3117: val = ((ts == null) ? null : ts.toString());
3118: // Following block truncates non-zero
3119: // sub-seconds from time types OTHER than
3120: // TIMESTAMP.
3121: if (dataType[insi] != java.sql.Types.TIMESTAMP
3122: && val != null) {
3123: dotAt = val.lastIndexOf('.');
3124: for (int z = dotAt + 1; z < val
3125: .length(); z++) {
3126: if (val.charAt(z) != '0') {
3127: dotAt = 0;
3128: break;
3129: }
3130: }
3131: if (dotAt > 1) {
3132: val = val.substring(0, dotAt);
3133: }
3134: }
3135: break;
3136: default:
3137: val = r.getString(i);
3138:
3139: // If we tried to get a String but it
3140: // failed, try getting it with a String
3141: // Stream
3142: if (val == null) {
3143: try {
3144: val = streamToString(r
3145: .getAsciiStream(i), charset);
3146: } catch (Exception e) {
3147: }
3148: }
3149: }
3150: }
3151:
3152: if (binary || (val == null && !r.wasNull())) {
3153: if (pwDsv != null) {
3154: throw new SqlToolError(rb
3155: .getString(SqltoolRB.DSV_BINCOL));
3156: }
3157:
3158: // DB has a value but we either explicitly want
3159: // it as binary, or we failed to get it as String.
3160: try {
3161: binBuffer = SqlFile.streamToBytes(r
3162: .getBinaryStream(i));
3163: } catch (IOException ioe) {
3164: throw new SqlToolError(
3165: "Failed to read value using stream",
3166: ioe);
3167: }
3168:
3169: stdprintln(rb
3170: .getString(
3171: SqltoolRB.BINBUF_WRITE,
3172: Integer
3173: .toString(binBuffer.length),
3174: headerArray[insi],
3175: SqlFile
3176: .sqlTypeToString(dataType[insi])));
3177:
3178: return;
3179: }
3180:
3181: if (excludeSysSchemas && val != null && i == 2) {
3182: for (int z = 0; z < oracleSysSchemas.length; z++) {
3183: if (val.equals(oracleSysSchemas[z])) {
3184: filteredOut = true;
3185:
3186: break;
3187: }
3188: }
3189: }
3190:
3191: userVars.put("?", ((val == null) ? nullRepToken
3192: : val));
3193: if (fetchingVar != null) {
3194: userVars.put(fetchingVar, userVars.get("?"));
3195: updateUserSettings();
3196:
3197: fetchingVar = null;
3198: }
3199:
3200: if (silent) {
3201: return;
3202: }
3203:
3204: // We do not omit rows here. We collect information
3205: // so we can make the decision after all rows are
3206: // read in.
3207: if (filter != null
3208: && (val == null || val.indexOf(filter) > -1)) {
3209: filteredOut = false;
3210: }
3211:
3212: ///////////////////////////////
3213: // A little tricky here. fieldArray[] MUST get set.
3214: if (val == null && pwDsv == null) {
3215: if (dataType[insi] == java.sql.Types.VARCHAR) {
3216: fieldArray[insi] = (htmlMode ? "<I>null</I>"
3217: : nullRepToken);
3218: } else {
3219: fieldArray[insi] = "";
3220: }
3221: } else {
3222: fieldArray[insi] = val;
3223: }
3224:
3225: ///////////////////////////////
3226: if (htmlMode || pwDsv != null) {
3227: continue;
3228: }
3229:
3230: if (fieldArray[insi].length() > maxWidth[insi]) {
3231: maxWidth[insi] = fieldArray[insi].length();
3232: }
3233: }
3234:
3235: if (!filteredOut) {
3236: rows.add(fieldArray);
3237: }
3238: }
3239:
3240: // STEP 2: DISPLAY DATA (= 2a OR 2b)
3241: // STEP 2a (Non-DSV)
3242: if (pwDsv == null) {
3243: condlPrintln("<TABLE border='1'>", true);
3244:
3245: if (incCount > 1) {
3246: condlPrint(SqlFile.htmlRow(COL_HEAD) + LS + PRE_TD,
3247: true);
3248:
3249: for (int i = 0; i < headerArray.length; i++) {
3250: condlPrint("<TD>" + headerArray[i] + "</TD>",
3251: true);
3252: condlPrint(
3253: ((i > 0) ? SqlFile.spaces(2) : "")
3254: + SqlFile
3255: .pad(
3256: headerArray[i],
3257: maxWidth[i],
3258: rightJust[i],
3259: (i < headerArray.length - 1 || rightJust[i])),
3260: false);
3261: }
3262:
3263: condlPrintln(LS + PRE_TR + "</TR>", true);
3264: condlPrintln("", false);
3265:
3266: if (!htmlMode) {
3267: for (int i = 0; i < headerArray.length; i++) {
3268: condlPrint(((i > 0) ? SqlFile.spaces(2)
3269: : "")
3270: + SqlFile.divider(maxWidth[i]),
3271: false);
3272: }
3273:
3274: condlPrintln("", false);
3275: }
3276: }
3277:
3278: for (int i = 0; i < rows.size(); i++) {
3279: condlPrint(SqlFile
3280: .htmlRow(((i % 2) == 0) ? COL_EVEN
3281: : COL_ODD)
3282: + LS + PRE_TD, true);
3283:
3284: fieldArray = (String[]) rows.get(i);
3285:
3286: for (int j = 0; j < fieldArray.length; j++) {
3287: condlPrint("<TD>" + fieldArray[j] + "</TD>",
3288: true);
3289: condlPrint(
3290: ((j > 0) ? SqlFile.spaces(2) : "")
3291: + SqlFile
3292: .pad(
3293: fieldArray[j],
3294: maxWidth[j],
3295: rightJust[j],
3296: (j < fieldArray.length - 1 || rightJust[j])),
3297: false);
3298: }
3299:
3300: condlPrintln(LS + PRE_TR + "</TR>", true);
3301: condlPrintln("", false);
3302: }
3303:
3304: condlPrintln("</TABLE>", true);
3305:
3306: if (rows.size() != 1) {
3307: stdprintln(LS
3308: + rb.getString(SqltoolRB.ROWS_FETCHED, rows
3309: .size()), true);
3310: }
3311:
3312: condlPrintln("<HR>", true);
3313:
3314: break;
3315: }
3316:
3317: // STEP 2b (DSV)
3318: if (incCount > 0) {
3319: for (int i = 0; i < headerArray.length; i++) {
3320: dsvSafe(headerArray[i]);
3321: pwDsv.print(headerArray[i]);
3322:
3323: if (i < headerArray.length - 1) {
3324: pwDsv.print(dsvColDelim);
3325: }
3326: }
3327:
3328: pwDsv.print(dsvRowDelim);
3329: }
3330:
3331: for (int i = 0; i < rows.size(); i++) {
3332: fieldArray = (String[]) rows.get(i);
3333:
3334: for (int j = 0; j < fieldArray.length; j++) {
3335: dsvSafe(fieldArray[j]);
3336: pwDsv
3337: .print((fieldArray[j] == null) ? (autonulls[j] ? ""
3338: : nullRepToken)
3339: : fieldArray[j]);
3340:
3341: if (j < fieldArray.length - 1) {
3342: pwDsv.print(dsvColDelim);
3343: }
3344: }
3345:
3346: pwDsv.print(dsvRowDelim);
3347: }
3348:
3349: stdprintln(rb.getString(SqltoolRB.ROWS_FETCHED_DSV, rows
3350: .size()));
3351: break;
3352:
3353: default:
3354: userVars.put("?", Integer.toString(updateCount));
3355: if (fetchingVar != null) {
3356: userVars.put(fetchingVar, userVars.get("?"));
3357: updateUserSettings();
3358: fetchingVar = null;
3359: }
3360:
3361: if (updateCount != 0) {
3362: stdprintln((updateCount == 1) ? rb
3363: .getString(SqltoolRB.ROW_UPDATE_SINGULAR) : rb
3364: .getString(SqltoolRB.ROW_UPDATE_MULTIPLE,
3365: updateCount));
3366: }
3367: break;
3368: }
3369: }
3370:
3371: private static final int COL_HEAD = 0, COL_ODD = 1, COL_EVEN = 2;
3372: private static final String PRE_TR = spaces(4);
3373: private static final String PRE_TD = spaces(8);
3374:
3375: /**
3376: * Print a properly formatted HTML <TR> command for the given
3377: * situation.
3378: *
3379: * @param colType Column type: COL_HEAD, COL_ODD or COL_EVEN.
3380: */
3381: private static String htmlRow(int colType) {
3382: switch (colType) {
3383: case COL_HEAD:
3384: return PRE_TR + "<TR style='font-weight: bold;'>";
3385:
3386: case COL_ODD:
3387: return PRE_TR
3388: + "<TR style='background: #94d6ef; font: normal "
3389: + "normal 10px/10px Arial, Helvitica, sans-serif;'>";
3390:
3391: case COL_EVEN:
3392: return PRE_TR
3393: + "<TR style='background: silver; font: normal "
3394: + "normal 10px/10px Arial, Helvitica, sans-serif;'>";
3395: }
3396:
3397: return null;
3398: }
3399:
3400: /**
3401: * Returns a divider of hypens of requested length.
3402: *
3403: * @param len Length of output String.
3404: */
3405: private static String divider(int len) {
3406: return (len > DIVIDER.length()) ? DIVIDER : DIVIDER.substring(
3407: 0, len);
3408: }
3409:
3410: /**
3411: * Returns a String of spaces of requested length.
3412: *
3413: * @param len Length of output String.
3414: */
3415: private static String spaces(int len) {
3416: return (len > SPACES.length()) ? SPACES : SPACES.substring(0,
3417: len);
3418: }
3419:
3420: /**
3421: * Pads given input string out to requested length with space
3422: * characters.
3423: *
3424: * @param inString Base string.
3425: * @param fulllen Output String length.
3426: * @param rightJustify True to right justify, false to left justify.
3427: */
3428: private static String pad(String inString, int fulllen,
3429: boolean rightJustify, boolean doPad) {
3430: if (!doPad) {
3431: return inString;
3432: }
3433:
3434: int len = fulllen - inString.length();
3435:
3436: if (len < 1) {
3437: return inString;
3438: }
3439:
3440: String pad = SqlFile.spaces(len);
3441:
3442: return ((rightJustify ? pad : "") + inString + (rightJustify ? ""
3443: : pad));
3444: }
3445:
3446: /**
3447: * Display command history.
3448: */
3449: private void showHistory() throws BadSpecial {
3450: if (history == null) {
3451: throw new BadSpecial(rb
3452: .getString(SqltoolRB.HISTORY_UNAVAILABLE));
3453: }
3454: if (history.size() < 1) {
3455: throw new BadSpecial(rb.getString(SqltoolRB.HISTORY_NONE));
3456: }
3457: for (int i = 0; i < history.size(); i++) {
3458: psStd.println("#" + (i + oldestHist) + " or "
3459: + (i - history.size()) + ':');
3460: psStd.println((String) history.get(i));
3461: }
3462: if (buffer != null) {
3463: psStd.println(rb.getString(SqltoolRB.EDITBUFFER_CONTENTS,
3464: buffer));
3465: }
3466:
3467: psStd.println();
3468: psStd.println(rb.getString(SqltoolRB.BUFFER_INSTRUCTIONS));
3469: }
3470:
3471: /**
3472: * Return a Command from command history.
3473: */
3474: private String commandFromHistory(int inIndex) throws BadSpecial {
3475: int index = inIndex; // Just to quiet compiler warnings.
3476:
3477: if (history == null) {
3478: throw new BadSpecial(rb
3479: .getString(SqltoolRB.HISTORY_UNAVAILABLE));
3480: }
3481: if (index == 0) {
3482: throw new BadSpecial(rb
3483: .getString(SqltoolRB.HISTORY_NUMBER_REQ));
3484: }
3485: if (index > 0) {
3486: // Positive command# given
3487: index -= oldestHist;
3488: if (index < 0) {
3489: throw new BadSpecial(rb.getString(
3490: SqltoolRB.HISTORY_BACKTO, oldestHist));
3491: }
3492: if (index >= history.size()) {
3493: throw new BadSpecial(rb.getString(
3494: SqltoolRB.HISTORY_UPTO, history.size()
3495: + oldestHist - 1));
3496: }
3497: } else {
3498: // Negative command# given
3499: index += history.size();
3500: if (index < 0) {
3501: throw new BadSpecial(rb.getString(
3502: SqltoolRB.HISTORY_BACK, history.size()));
3503: }
3504: }
3505: return (String) history.get(index);
3506: }
3507:
3508: /**
3509: * Search Command History for a regex match.
3510: *
3511: * @returns Absolute command number, if any match.
3512: */
3513: private Integer historySearch(String findRegex) throws BadSpecial {
3514: if (history == null) {
3515: throw new BadSpecial(rb
3516: .getString(SqltoolRB.HISTORY_UNAVAILABLE));
3517: }
3518: Pattern pattern = Pattern.compile("(?ims)" + findRegex);
3519: // Make matching more liberal. Users can customize search behavior
3520: // by using "(?-OPTIONS)" or (?OPTIONS) in their regexes.
3521: for (int index = history.size() - 1; index >= 0; index--)
3522: if (pattern.matcher((String) history.get(index)).find())
3523: return new Integer(index + oldestHist);
3524: return null;
3525: }
3526:
3527: private void setBuf(String newContent) {
3528: buffer = new String(newContent);
3529: // System.err.println("Buffer is now (" + buffer + ')');
3530: }
3531:
3532: int oldestHist = 1;
3533:
3534: /**
3535: * Add a command onto the history list.
3536: */
3537: private void historize() {
3538: if (history == null || buffer == null) {
3539: return;
3540: }
3541: if (history.size() > 0
3542: && history.get(history.size() - 1).equals(buffer)) {
3543: // Don't store two consecutive commands that are exactly the same.
3544: return;
3545: }
3546: history.add(buffer);
3547: if (history.size() <= maxHistoryLength) {
3548: return;
3549: }
3550: history.remove(0);
3551: oldestHist++;
3552: }
3553:
3554: /**
3555: * Describe the columns of specified table.
3556: *
3557: * @param tableName Table that will be described.
3558: * @param filter Substring to filter by
3559: */
3560: private void describe(String tableName, String inFilter)
3561: throws SQLException {
3562: /*
3563: * Doing case-sensitive filters now, for greater portability.
3564: String filter = ((inFilter == null) ? null : inFilter.toUpperCase());
3565: */
3566: String filter = inFilter;
3567: List rows = new ArrayList();
3568: String[] headerArray = {
3569: rb.getString(SqltoolRB.DESCRIBE_TABLE_NAME),
3570: rb.getString(SqltoolRB.DESCRIBE_TABLE_DATATYPE),
3571: rb.getString(SqltoolRB.DESCRIBE_TABLE_WIDTH),
3572: rb.getString(SqltoolRB.DESCRIBE_TABLE_NONULLS), };
3573: String[] fieldArray;
3574: int[] maxWidth = { 0, 0, 0, 0 };
3575: boolean[] rightJust = { false, false, true, false };
3576:
3577: // STEP 1: GATHER DATA
3578: for (int i = 0; i < headerArray.length; i++) {
3579: if (htmlMode) {
3580: continue;
3581: }
3582:
3583: if (headerArray[i].length() > maxWidth[i]) {
3584: maxWidth[i] = headerArray[i].length();
3585: }
3586: }
3587:
3588: Statement statement = curConn.createStatement();
3589: ResultSet r = null;
3590:
3591: try {
3592: statement.execute("SELECT * FROM " + tableName
3593: + " WHERE 1 = 2");
3594:
3595: r = statement.getResultSet();
3596:
3597: ResultSetMetaData m = r.getMetaData();
3598: int cols = m.getColumnCount();
3599:
3600: for (int i = 0; i < cols; i++) {
3601: fieldArray = new String[4];
3602: fieldArray[0] = m.getColumnName(i + 1);
3603:
3604: if (filter != null && fieldArray[0].indexOf(filter) < 0) {
3605: continue;
3606: }
3607:
3608: fieldArray[1] = m.getColumnTypeName(i + 1);
3609: fieldArray[2] = Integer.toString(m
3610: .getColumnDisplaySize(i + 1));
3611: fieldArray[3] = ((m.isNullable(i + 1) == java.sql.ResultSetMetaData.columnNullable) ? (htmlMode ? " "
3612: : "")
3613: : "*");
3614:
3615: rows.add(fieldArray);
3616:
3617: for (int j = 0; j < fieldArray.length; j++) {
3618: if (fieldArray[j].length() > maxWidth[j]) {
3619: maxWidth[j] = fieldArray[j].length();
3620: }
3621: }
3622: }
3623:
3624: // STEP 2: DISPLAY DATA
3625: condlPrint("<TABLE border='1'>" + LS
3626: + SqlFile.htmlRow(COL_HEAD) + LS + PRE_TD, true);
3627:
3628: for (int i = 0; i < headerArray.length; i++) {
3629: condlPrint("<TD>" + headerArray[i] + "</TD>", true);
3630: condlPrint(
3631: ((i > 0) ? SqlFile.spaces(2) : "")
3632: + SqlFile
3633: .pad(
3634: headerArray[i],
3635: maxWidth[i],
3636: rightJust[i],
3637: (i < headerArray.length - 1 || rightJust[i])),
3638: false);
3639: }
3640:
3641: condlPrintln(LS + PRE_TR + "</TR>", true);
3642: condlPrintln("", false);
3643:
3644: if (!htmlMode) {
3645: for (int i = 0; i < headerArray.length; i++) {
3646: condlPrint(((i > 0) ? SqlFile.spaces(2) : "")
3647: + SqlFile.divider(maxWidth[i]), false);
3648: }
3649:
3650: condlPrintln("", false);
3651: }
3652:
3653: for (int i = 0; i < rows.size(); i++) {
3654: condlPrint(SqlFile.htmlRow(((i % 2) == 0) ? COL_EVEN
3655: : COL_ODD)
3656: + LS + PRE_TD, true);
3657:
3658: fieldArray = (String[]) rows.get(i);
3659:
3660: for (int j = 0; j < fieldArray.length; j++) {
3661: condlPrint("<TD>" + fieldArray[j] + "</TD>", true);
3662: condlPrint(
3663: ((j > 0) ? SqlFile.spaces(2) : "")
3664: + SqlFile
3665: .pad(
3666: fieldArray[j],
3667: maxWidth[j],
3668: rightJust[j],
3669: (j < fieldArray.length - 1 || rightJust[j])),
3670: false);
3671: }
3672:
3673: condlPrintln(LS + PRE_TR + "</TR>", true);
3674: condlPrintln("", false);
3675: }
3676:
3677: condlPrintln(LS + "</TABLE>" + LS + "<HR>", true);
3678: } finally {
3679: try {
3680: if (r != null) {
3681: r.close();
3682:
3683: r = null;
3684: }
3685:
3686: statement.close();
3687: } catch (Exception e) {
3688: }
3689: }
3690: }
3691:
3692: private boolean eval(String[] inTokens) throws BadSpecial {
3693: /* TODO: Rewrite using java.util.regex. */
3694: // dereference *VARNAME variables.
3695: // N.b. we work with a "copy" of the tokens.
3696: boolean negate = inTokens.length > 0 && inTokens[0].equals("!");
3697: String[] tokens = new String[negate ? (inTokens.length - 1)
3698: : inTokens.length];
3699: String inToken;
3700:
3701: for (int i = 0; i < tokens.length; i++) {
3702: inToken = inTokens[i + (negate ? 1 : 0)];
3703: if (inToken.length() > 1 && inToken.charAt(0) == '*') {
3704: tokens[i] = (String) userVars.get(inToken.substring(1));
3705: } else {
3706: tokens[i] = inTokens[i + (negate ? 1 : 0)];
3707: }
3708:
3709: // Unset variables permitted in expressions as long as use
3710: // the short *VARNAME form.
3711: if (tokens[i] == null) {
3712: tokens[i] = "";
3713: }
3714: }
3715:
3716: if (tokens.length == 1) {
3717: return (tokens[0].length() > 0 && !tokens[0].equals("0"))
3718: ^ negate;
3719: }
3720:
3721: if (tokens.length == 3) {
3722: if (tokens[1].equals("==")) {
3723: return tokens[0].equals(tokens[2]) ^ negate;
3724: }
3725:
3726: if (tokens[1].equals("!=") || tokens[1].equals("<>")
3727: || tokens[1].equals("><")) {
3728: return (!tokens[0].equals(tokens[2])) ^ negate;
3729: }
3730:
3731: if (tokens[1].equals(">")) {
3732: return (tokens[0].length() > tokens[2].length() || ((tokens[0]
3733: .length() == tokens[2].length()) && tokens[0]
3734: .compareTo(tokens[2]) > 0))
3735: ^ negate;
3736: }
3737:
3738: if (tokens[1].equals("<")) {
3739: return (tokens[2].length() > tokens[0].length() || ((tokens[2]
3740: .length() == tokens[0].length()) && tokens[2]
3741: .compareTo(tokens[0]) > 0))
3742: ^ negate;
3743: }
3744: }
3745:
3746: throw new BadSpecial(rb
3747: .getString(SqltoolRB.LOGICAL_UNRECOGNIZED));
3748: }
3749:
3750: private void closeQueryOutputStream() {
3751: if (pwQuery == null) {
3752: return;
3753: }
3754:
3755: if (htmlMode) {
3756: pwQuery.println("</BODY></HTML>");
3757: pwQuery.flush();
3758: }
3759:
3760: pwQuery.close();
3761:
3762: pwQuery = null;
3763: }
3764:
3765: /**
3766: * Print to psStd and possibly pwQuery iff current HTML mode matches
3767: * supplied printHtml.
3768: */
3769: private void condlPrintln(String s, boolean printHtml) {
3770: if ((printHtml && !htmlMode) || (htmlMode && !printHtml)) {
3771: return;
3772: }
3773:
3774: psStd.println(s);
3775:
3776: if (pwQuery != null) {
3777: pwQuery.println(s);
3778: pwQuery.flush();
3779: }
3780: }
3781:
3782: /**
3783: * Print to psStd and possibly pwQuery iff current HTML mode matches
3784: * supplied printHtml.
3785: */
3786: private void condlPrint(String s, boolean printHtml) {
3787: if ((printHtml && !htmlMode) || (htmlMode && !printHtml)) {
3788: return;
3789: }
3790:
3791: psStd.print(s);
3792:
3793: if (pwQuery != null) {
3794: pwQuery.print(s);
3795: pwQuery.flush();
3796: }
3797: }
3798:
3799: private String formatNicely(Map map, boolean withValues) {
3800: String key;
3801: StringBuffer sb = new StringBuffer();
3802: Iterator it = (new TreeMap(map)).keySet().iterator();
3803:
3804: if (withValues) {
3805: SqlFile.appendLine(sb, rb
3806: .getString(SqltoolRB.PL_LIST_PARENS));
3807: } else {
3808: SqlFile.appendLine(sb, rb
3809: .getString(SqltoolRB.PL_LIST_LENGTHS));
3810: }
3811:
3812: while (it.hasNext()) {
3813: key = (String) it.next();
3814:
3815: String s = (String) map.get(key);
3816:
3817: SqlFile.appendLine(sb, " "
3818: + key
3819: + ": "
3820: + (withValues ? ("(" + s + ')') : Integer
3821: .toString(s.length())));
3822: }
3823:
3824: return sb.toString();
3825: }
3826:
3827: /**
3828: * Ascii file dump.
3829: *
3830: * dumpFile must not be null.
3831: */
3832: private void dump(String varName, File dumpFile)
3833: throws IOException, BadSpecial {
3834: String val = (String) userVars.get(varName);
3835:
3836: if (val == null) {
3837: throw new BadSpecial(rb.getString(
3838: SqltoolRB.PLVAR_UNDEFINED, varName));
3839: }
3840:
3841: OutputStreamWriter osw = ((charset == null) ? (new OutputStreamWriter(
3842: new FileOutputStream(dumpFile)))
3843: : (new OutputStreamWriter(
3844: new FileOutputStream(dumpFile), charset)));
3845: // Replace with just "(new FileOutputStream(file), charset)"
3846: // once use defaultCharset from Java 1.5 in charset init. above.
3847:
3848: try {
3849: osw.write(val);
3850:
3851: if (val.length() > 0) {
3852: char lastChar = val.charAt(val.length() - 1);
3853:
3854: if (lastChar != '\n' && lastChar != '\r') {
3855: osw.write(LS);
3856: }
3857: }
3858:
3859: osw.flush();
3860: } finally {
3861: osw.close();
3862: }
3863:
3864: // Since opened in overwrite mode, since we didn't exception out,
3865: // we can be confident that we wrote all the bytest in the file.
3866: stdprintln(rb.getString(SqltoolRB.FILE_WROTECHARS, Long
3867: .toString(dumpFile.length()), dumpFile.toString()));
3868: }
3869:
3870: byte[] binBuffer = null;
3871:
3872: /**
3873: * Binary file dump
3874: *
3875: * dumpFile must not be null.
3876: */
3877: private void dump(File dumpFile) throws IOException, BadSpecial {
3878: if (binBuffer == null) {
3879: throw new BadSpecial(rb
3880: .getString(SqltoolRB.BINBUFFER_EMPTY));
3881: }
3882:
3883: FileOutputStream fos = new FileOutputStream(dumpFile);
3884: int len = 0;
3885:
3886: try {
3887: fos.write(binBuffer);
3888:
3889: len = binBuffer.length;
3890:
3891: binBuffer = null;
3892:
3893: fos.flush();
3894: } finally {
3895: fos.close();
3896: }
3897: stdprintln(rb.getString(SqltoolRB.FILE_WROTECHARS, len,
3898: dumpFile.toString()));
3899: }
3900:
3901: /**
3902: * As the name says...
3903: * This method always closes the input stream.
3904: */
3905: public String streamToString(InputStream is, String cs)
3906: throws IOException {
3907: try {
3908: byte[] ba = null;
3909: int bytesread = 0;
3910: int retval;
3911: try {
3912: ba = new byte[is.available()];
3913: } catch (RuntimeException re) {
3914: throw new IOException(rb
3915: .getString(SqltoolRB.READ_TOOBIG));
3916: }
3917: while (bytesread < ba.length
3918: && (retval = is.read(ba, bytesread, ba.length
3919: - bytesread)) > 0) {
3920: bytesread += retval;
3921: }
3922: if (bytesread != ba.length) {
3923: throw new IOException(rb.getString(
3924: SqltoolRB.READ_PARTIAL, bytesread, ba.length));
3925: }
3926: try {
3927: return (cs == null) ? (new String(ba)) : (new String(
3928: ba, cs));
3929: } catch (UnsupportedEncodingException uee) {
3930: throw new RuntimeException(uee);
3931: } catch (RuntimeException re) {
3932: throw new IOException(rb
3933: .getString(SqltoolRB.READ_CONVERTFAIL));
3934: }
3935: } finally {
3936: is.close();
3937: }
3938: }
3939:
3940: /**
3941: * Ascii file load.
3942: */
3943: private void load(String varName, File asciiFile, String cs)
3944: throws IOException {
3945: String string = streamToString(new FileInputStream(asciiFile),
3946: cs);
3947: userVars.put(varName, string);
3948: updateUserSettings();
3949: }
3950:
3951: static public byte[] streamToBytes(InputStream is)
3952: throws IOException {
3953: byte[] xferBuffer = new byte[10240];
3954: ByteArrayOutputStream baos = new ByteArrayOutputStream();
3955: int i;
3956:
3957: while ((i = is.read(xferBuffer)) > 0) {
3958: baos.write(xferBuffer, 0, i);
3959: }
3960:
3961: return baos.toByteArray();
3962: }
3963:
3964: /**
3965: * Binary file load
3966: */
3967: static public byte[] loadBinary(File binFile) throws IOException {
3968: byte[] xferBuffer = new byte[10240];
3969: ByteArrayOutputStream baos = new ByteArrayOutputStream();
3970: int i;
3971: FileInputStream fis = new FileInputStream(binFile);
3972:
3973: try {
3974: while ((i = fis.read(xferBuffer)) > 0) {
3975: baos.write(xferBuffer, 0, i);
3976: }
3977: } finally {
3978: fis.close();
3979: }
3980:
3981: byte[] ba = baos.toByteArray();
3982:
3983: return ba;
3984: }
3985:
3986: /**
3987: * This method is used to tell SqlFile whether this Sql Type must
3988: * ALWAYS be loaded to the binary buffer without displaying.
3989: *
3990: * N.b.: If this returns "true" for a type, then the user can never
3991: * "see" values for these columns.
3992: * Therefore, if a type may-or-may-not-be displayable, better to return
3993: * false here and let the user choose.
3994: * In general, if there is a toString() operator for this Sql Type
3995: * then return false, since the JDBC driver should know how to make the
3996: * value displayable.
3997: *
3998: * The table on this page lists the most common SqlTypes, all of which
3999: * must implement toString():
4000: * http://java.sun.com/docs/books/tutorial/jdbc/basics/retrieving.html
4001: *
4002: * @see java.sql.Types
4003: */
4004: public static boolean canDisplayType(int i) {
4005: /* I don't now about some of the more obscure types, like REF and
4006: * DATALINK */
4007: switch (i) {
4008: //case java.sql.Types.BINARY :
4009: case java.sql.Types.BLOB:
4010: case java.sql.Types.JAVA_OBJECT:
4011:
4012: //case java.sql.Types.LONGVARBINARY :
4013: //case java.sql.Types.LONGVARCHAR :
4014: case java.sql.Types.OTHER:
4015: case java.sql.Types.STRUCT:
4016:
4017: //case java.sql.Types.VARBINARY :
4018: return false;
4019: }
4020:
4021: return true;
4022: }
4023:
4024: // won't compile with JDK 1.3 without these
4025: private static final int JDBC3_BOOLEAN = 16;
4026: private static final int JDBC3_DATALINK = 70;
4027:
4028: public static String sqlTypeToString(int i) {
4029: switch (i) {
4030: case java.sql.Types.ARRAY:
4031: return "ARRAY";
4032:
4033: case java.sql.Types.BIGINT:
4034: return "BIGINT";
4035:
4036: case java.sql.Types.BINARY:
4037: return "BINARY";
4038:
4039: case java.sql.Types.BIT:
4040: return "BIT";
4041:
4042: case java.sql.Types.BLOB:
4043: return "BLOB";
4044:
4045: case JDBC3_BOOLEAN:
4046: return "BOOLEAN";
4047:
4048: case java.sql.Types.CHAR:
4049: return "CHAR";
4050:
4051: case java.sql.Types.CLOB:
4052: return "CLOB";
4053:
4054: case JDBC3_DATALINK:
4055: return "DATALINK";
4056:
4057: case java.sql.Types.DATE:
4058: return "DATE";
4059:
4060: case java.sql.Types.DECIMAL:
4061: return "DECIMAL";
4062:
4063: case java.sql.Types.DISTINCT:
4064: return "DISTINCT";
4065:
4066: case java.sql.Types.DOUBLE:
4067: return "DOUBLE";
4068:
4069: case java.sql.Types.FLOAT:
4070: return "FLOAT";
4071:
4072: case java.sql.Types.INTEGER:
4073: return "INTEGER";
4074:
4075: case java.sql.Types.JAVA_OBJECT:
4076: return "JAVA_OBJECT";
4077:
4078: case java.sql.Types.LONGVARBINARY:
4079: return "LONGVARBINARY";
4080:
4081: case java.sql.Types.LONGVARCHAR:
4082: return "LONGVARCHAR";
4083:
4084: case java.sql.Types.NULL:
4085: return "NULL";
4086:
4087: case java.sql.Types.NUMERIC:
4088: return "NUMERIC";
4089:
4090: case java.sql.Types.OTHER:
4091: return "OTHER";
4092:
4093: case java.sql.Types.REAL:
4094: return "REAL";
4095:
4096: case java.sql.Types.REF:
4097: return "REF";
4098:
4099: case java.sql.Types.SMALLINT:
4100: return "SMALLINT";
4101:
4102: case java.sql.Types.STRUCT:
4103: return "STRUCT";
4104:
4105: case java.sql.Types.TIME:
4106: return "TIME";
4107:
4108: case java.sql.Types.TIMESTAMP:
4109: return "TIMESTAMP";
4110:
4111: case java.sql.Types.TINYINT:
4112: return "TINYINT";
4113:
4114: case java.sql.Types.VARBINARY:
4115: return "VARBINARY";
4116:
4117: case java.sql.Types.VARCHAR:
4118: return "VARCHAR";
4119: }
4120:
4121: return "Unknown type " + i;
4122: }
4123:
4124: /**
4125: * Validate that String is safe to display in a DSV file.
4126: *
4127: * @throws SqlToolError if validation fails.
4128: */
4129: public void dsvSafe(String s) throws SqlToolError {
4130: if (pwDsv == null || dsvColDelim == null || dsvRowDelim == null
4131: || nullRepToken == null) {
4132: throw new RuntimeException("Assertion failed. \n"
4133: + "dsvSafe called when DSV settings are incomplete");
4134: }
4135:
4136: if (s == null) {
4137: return;
4138: }
4139:
4140: if (s.indexOf(dsvColDelim) > 0) {
4141: throw new SqlToolError(rb.getString(
4142: SqltoolRB.DSV_COLDELIM_PRESENT, dsvColDelim));
4143: }
4144:
4145: if (s.indexOf(dsvRowDelim) > 0) {
4146: throw new SqlToolError(rb.getString(
4147: SqltoolRB.DSV_ROWDELIM_PRESENT, dsvRowDelim));
4148: }
4149:
4150: if (s.trim().equals(nullRepToken)) {
4151: // The trim() is to avoid the situation where the contents of a
4152: // field "looks like" the null-rep token.
4153: throw new SqlToolError(rb.getString(
4154: SqltoolRB.DSV_NULLREP_PRESENT, nullRepToken));
4155: }
4156: }
4157:
4158: /**
4159: * Translates user-supplied escapes into the traditionaly corresponding
4160: * corresponding binary characters.
4161: *
4162: * Allowed sequences:
4163: * <UL>
4164: * <LI>\0\d+ (an octal digit)
4165: * <LI>\[0-9]\d* (a decimal digit)
4166: * <LI>\[Xx][0-9]{2} (a hex digit)
4167: * <LI>\n Newline (Ctrl-J)
4168: * <LI>\r Carriage return (Ctrl-M)
4169: * <LI>\t Horizontal tab (Ctrl-I)
4170: * <LI>\f Form feed (Ctrl-L)
4171: * </UL>
4172: *
4173: * Java 1.4 String methods will make this into a 1 or 2 line task.
4174: */
4175: public static String convertEscapes(String inString) {
4176: if (inString == null) {
4177: return null;
4178: }
4179: return convertNumericEscapes(convertEscapes(convertEscapes(
4180: convertEscapes(convertEscapes(convertEscapes(inString,
4181: "\\n", "\n"), "\\r", "\r"), "\\t", "\t"),
4182: "\\\\", "\\"), "\\f", "\f"));
4183: }
4184:
4185: /**
4186: * @param string Non-null String to modify.
4187: */
4188: private static String convertNumericEscapes(String string) {
4189: String workString = string;
4190: int i = 0;
4191:
4192: for (char dig = '0'; dig <= '9'; dig++) {
4193: while ((i = workString.indexOf("\\" + dig, i)) > -1
4194: && i < workString.length() - 1) {
4195: workString = convertNumericEscape(string, i);
4196: }
4197: while ((i = workString.indexOf("\\x" + dig, i)) > -1
4198: && i < workString.length() - 1) {
4199: workString = convertNumericEscape(string, i);
4200: }
4201: while ((i = workString.indexOf("\\X" + dig, i)) > -1
4202: && i < workString.length() - 1) {
4203: workString = convertNumericEscape(string, i);
4204: }
4205: }
4206: return workString;
4207: }
4208:
4209: /**
4210: * @offset Position of the leading \.
4211: */
4212: private static String convertNumericEscape(String string, int offset) {
4213: int post = -1;
4214: int firstDigit = -1;
4215: int radix = -1;
4216: if (Character.toUpperCase(string.charAt(offset + 1)) == 'X') {
4217: firstDigit = offset + 2;
4218: radix = 16;
4219: post = firstDigit + 2;
4220: if (post > string.length())
4221: post = string.length();
4222: } else {
4223: firstDigit = offset + 1;
4224: radix = (Character.toUpperCase(string.charAt(firstDigit)) == '0') ? 8
4225: : 10;
4226: for (post = firstDigit + 1; post < string.length()
4227: && Character.isDigit(string.charAt(post)); post++)
4228: ;
4229: }
4230: return string.substring(0, offset)
4231: + ((char) Integer.parseInt(string.substring(firstDigit,
4232: post), radix)) + string.substring(post);
4233: }
4234:
4235: /**
4236: * @param string Non-null String to modify.
4237: */
4238: private static String convertEscapes(String string, String from,
4239: String to) {
4240: String workString = string;
4241: int i = 0;
4242: int fromLen = from.length();
4243:
4244: while ((i = workString.indexOf(from, i)) > -1
4245: && i < workString.length() - 1) {
4246: workString = workString.substring(0, i) + to
4247: + workString.substring(i + fromLen);
4248: }
4249: return workString;
4250: }
4251:
4252: /**
4253: * Name is self-explanatory.
4254: *
4255: * If there is user demand, open file in random access mode so don't
4256: * need to load 2 copies of the entire file into memory.
4257: * This will be difficult because can't use standard Java language
4258: * features to search through a character array for multi-character
4259: * substrings.
4260: *
4261: * @throws SqlToolError Would prefer to throw an internal exception,
4262: * but we want this method to have external
4263: * visibility.
4264: */
4265: public void importDsv(String filePath, String skipPrefix)
4266: throws SqlToolError {
4267: /* To make string comparisons, contains() methods, etc. a little
4268: * simpler and concise, just switch all column names to lower-case.
4269: * This is ok since we acknowledge up from that DSV import/export
4270: * assume no special characters or escaping in column names. */
4271: byte[] bfr = null;
4272: File file = new File(filePath);
4273: SortedMap constColMap = null;
4274: if (dsvConstCols != null) {
4275: // We trim col. names, but not values. Must allow users to
4276: // specify values as spaces, empty string, null.
4277: constColMap = new TreeMap();
4278: String[] constPairs = dsvConstCols.split("\\Q"
4279: + dsvColDelim + "\\E\\s*");
4280: int firstEq;
4281: String n;
4282: for (int i = 0; i < constPairs.length; i++) {
4283: firstEq = constPairs[i].indexOf('=');
4284: n = constPairs[i].substring(0, firstEq).trim()
4285: .toLowerCase();
4286: if (n.trim().length() < 1) {
4287: throw new SqlToolError(rb
4288: .getString(SqltoolRB.DSV_CONSTCOLS_NULLCOL));
4289: }
4290: constColMap
4291: .put(n, constPairs[i].substring(firstEq + 1));
4292: }
4293: }
4294: Set skipCols = null;
4295: if (dsvSkipCols != null) {
4296: skipCols = new HashSet();
4297: String[] skipColsArray = dsvSkipCols.split("\\s*\\Q"
4298: + dsvColDelim + "\\E\\s*");
4299: for (int i = 0; i < skipColsArray.length; i++) {
4300: skipCols.add(skipColsArray[i].toLowerCase());
4301: }
4302: }
4303:
4304: if (!file.canRead()) {
4305: throw new SqlToolError(rb.getString(
4306: SqltoolRB.FILE_READFAIL, file.toString()));
4307: }
4308:
4309: try {
4310: bfr = new byte[(int) file.length()];
4311: } catch (RuntimeException re) {
4312: throw new SqlToolError(rb.getString(SqltoolRB.READ_TOOBIG),
4313: re);
4314: }
4315:
4316: int bytesread = 0;
4317: int retval;
4318: InputStream is = null;
4319:
4320: try {
4321: is = new FileInputStream(file);
4322: while (bytesread < bfr.length
4323: && (retval = is.read(bfr, bytesread, bfr.length
4324: - bytesread)) > 0) {
4325: bytesread += retval;
4326: }
4327:
4328: } catch (IOException ioe) {
4329: throw new SqlToolError(ioe);
4330: } finally {
4331: if (is != null)
4332: try {
4333: is.close();
4334: } catch (IOException ioe) {
4335: errprintln(rb
4336: .getString(SqltoolRB.INPUTFILE_CLOSEFAIL)
4337: + ": " + ioe);
4338: }
4339: }
4340: if (bytesread != bfr.length) {
4341: throw new SqlToolError(rb.getString(SqltoolRB.READ_PARTIAL,
4342: bytesread, bfr.length));
4343: }
4344:
4345: String string = null;
4346: String dateString;
4347:
4348: try {
4349: string = ((charset == null) ? (new String(bfr))
4350: : (new String(bfr, charset)));
4351: } catch (UnsupportedEncodingException uee) {
4352: throw new RuntimeException(uee);
4353: } catch (RuntimeException re) {
4354: throw new SqlToolError(rb
4355: .getString(SqltoolRB.READ_CONVERTFAIL), re);
4356: }
4357:
4358: List headerList = new ArrayList();
4359: String tableName = dsvTargetTable;
4360:
4361: // N.b. ENDs are the index of 1 PAST the current item
4362: int recEnd = -1000; // Recognizable value incase something goes
4363: // horrifically wrong.
4364: int colStart;
4365: int colEnd;
4366:
4367: // First read one until we get one header line
4368: int lineCount = 0; // Assume a 1 line header?
4369: int recStart = -1;
4370: String trimmedLine = null;
4371: boolean switching = false;
4372:
4373: while (true) {
4374: recStart = (recStart < 0) ? 0 : (recEnd + dsvRowDelim
4375: .length());
4376: if (recStart > string.length() - 2) {
4377: throw new SqlToolError(rb
4378: .getString(SqltoolRB.DSV_HEADER_NONE));
4379: }
4380: recEnd = string.indexOf(dsvRowDelim, recStart);
4381: lineCount++; // Increment when we have line start and end
4382:
4383: if (recEnd < 0) {
4384: // Last line in file. No data records.
4385: recEnd = string.length();
4386: }
4387: trimmedLine = string.substring(recStart, recEnd).trim();
4388: if (trimmedLine.length() < 1
4389: || (skipPrefix != null && trimmedLine
4390: .startsWith(skipPrefix))) {
4391: continue;
4392: }
4393: if (trimmedLine.startsWith("targettable=")) {
4394: if (tableName == null) {
4395: tableName = trimmedLine.substring(
4396: "targettable=".length()).trim();
4397: }
4398: continue;
4399: }
4400: if (trimmedLine.equals("headerswitch{")) {
4401: if (tableName == null) {
4402: throw new SqlToolError(rb.getString(
4403: SqltoolRB.DSV_HEADER_NOSWITCHTARG,
4404: lineCount));
4405: }
4406: switching = true;
4407: continue;
4408: }
4409: if (trimmedLine.equals("}")) {
4410: throw new SqlToolError(rb.getString(
4411: SqltoolRB.DSV_HEADER_NOSWITCHMATCH, lineCount));
4412: }
4413: if (!switching) {
4414: break;
4415: }
4416: int colonAt = trimmedLine.indexOf(':');
4417: if (colonAt < 1 || colonAt == trimmedLine.length() - 1) {
4418: throw new SqlToolError(rb.getString(
4419: SqltoolRB.DSV_HEADER_NONSWITCHED, lineCount));
4420: }
4421: String matcher = trimmedLine.substring(0, colonAt).trim();
4422: // Need to be sure here that tableName is not null (in
4423: // which case it would be determined later on by the file name).
4424: if (matcher.equals("*")
4425: || matcher.equalsIgnoreCase(tableName)) {
4426: recStart = 1 + string.indexOf(':', recStart);
4427: break;
4428: }
4429: // Skip non-matched header line
4430: }
4431:
4432: String headerLine = string.substring(recStart, recEnd);
4433: colStart = recStart;
4434: colEnd = -1;
4435: String colName;
4436:
4437: while (true) {
4438: if (colEnd == recEnd) {
4439: // We processed final column last time through loop
4440: break;
4441: }
4442:
4443: colEnd = string.indexOf(dsvColDelim, colStart);
4444:
4445: if (colEnd < 0 || colEnd > recEnd) {
4446: colEnd = recEnd;
4447: }
4448:
4449: if (colEnd - colStart < 1) {
4450: throw new SqlToolError(rb.getString(
4451: SqltoolRB.DSV_NOCOLHEADER,
4452: headerList.size() + 1, lineCount));
4453: }
4454:
4455: colName = string.substring(colStart, colEnd).trim()
4456: .toLowerCase();
4457: headerList
4458: .add((colName.equals("-")
4459: || (skipCols != null && skipCols
4460: .remove(colName)) || (constColMap != null && constColMap
4461: .containsKey(colName))) ? ((String) null)
4462: : colName);
4463:
4464: colStart = colEnd + dsvColDelim.length();
4465: }
4466: if (skipCols != null && skipCols.size() > 0) {
4467: throw new SqlToolError(rb
4468: .getString(SqltoolRB.DSV_SKIPCOLS_MISSING, skipCols
4469: .toString()));
4470: }
4471:
4472: boolean oneCol = false; // At least 1 non-null column
4473: for (int i = 0; i < headerList.size(); i++) {
4474: if (headerList.get(i) != null) {
4475: oneCol = true;
4476: break;
4477: }
4478: }
4479: if (oneCol == false) {
4480: // Difficult call, but I think in any real-world situation, the
4481: // user will want to know if they are inserting records with no
4482: // data from their input file.
4483: throw new SqlToolError(rb.getString(
4484: SqltoolRB.DSV_NOCOLSLEFT, dsvSkipCols));
4485: }
4486:
4487: int inputColHeadCount = headerList.size();
4488:
4489: if (constColMap != null) {
4490: headerList.addAll(constColMap.keySet());
4491: }
4492:
4493: String[] headers = (String[]) headerList.toArray(new String[0]);
4494: // headers contains input headers + all constCols, some of these
4495: // values may be nulls.
4496:
4497: if (tableName == null) {
4498: tableName = file.getName();
4499:
4500: int i = tableName.lastIndexOf('.');
4501:
4502: if (i > 0) {
4503: tableName = tableName.substring(0, i);
4504: }
4505: }
4506:
4507: StringBuffer tmpSb = new StringBuffer();
4508: List tmpList = new ArrayList();
4509:
4510: int skippers = 0;
4511: for (int i = 0; i < headers.length; i++) {
4512: if (headers[i] == null) {
4513: skippers++;
4514: continue;
4515: }
4516: if (tmpSb.length() > 0) {
4517: tmpSb.append(", ");
4518: }
4519:
4520: tmpSb.append(headers[i]);
4521: tmpList.add(headers[i]);
4522: }
4523: boolean[] autonulls = new boolean[headers.length - skippers];
4524: boolean[] parseDate = new boolean[autonulls.length];
4525: boolean[] parseBool = new boolean[autonulls.length];
4526: String[] insertFieldName = (String[]) tmpList
4527: .toArray(new String[] {});
4528: // Remember that the headers array has all columns in DSV file,
4529: // even skipped columns.
4530: // The autonulls array only has columns that we will insert into.
4531:
4532: StringBuffer sb = new StringBuffer("INSERT INTO " + tableName
4533: + " (" + tmpSb + ") VALUES (");
4534: StringBuffer typeQuerySb = new StringBuffer("SELECT " + tmpSb
4535: + " FROM " + tableName + " WHERE 1 = 2");
4536:
4537: try {
4538: ResultSetMetaData rsmd = curConn.createStatement()
4539: .executeQuery(typeQuerySb.toString()).getMetaData();
4540:
4541: if (rsmd.getColumnCount() != autonulls.length) {
4542: throw new SqlToolError(rb
4543: .getString(SqltoolRB.DSV_METADATA_MISMATCH));
4544: // Don't know if it's possible to get here.
4545: // If so, it's probably a SqlTool problem, not a user or
4546: // data problem.
4547: // Should be researched and either return a user-friendly
4548: // message or a RuntimeExceptin.
4549: }
4550:
4551: for (int i = 0; i < autonulls.length; i++) {
4552: autonulls[i] = true;
4553: parseDate[i] = false;
4554: parseBool[i] = false;
4555: switch (rsmd.getColumnType(i + 1)) {
4556: case java.sql.Types.BOOLEAN:
4557: parseBool[i] = true;
4558: break;
4559: case java.sql.Types.VARBINARY:
4560: case java.sql.Types.VARCHAR:
4561: case java.sql.Types.ARRAY:
4562: // Guessing at how to handle ARRAY.
4563: case java.sql.Types.BLOB:
4564: case java.sql.Types.CLOB:
4565: case java.sql.Types.LONGVARBINARY:
4566: case java.sql.Types.LONGVARCHAR:
4567: autonulls[i] = false;
4568: // This means to preserve white space and to insert
4569: // "" for "". Otherwise we trim white space and
4570: // insert null for \s*.
4571: break;
4572: case java.sql.Types.DATE:
4573: case java.sql.Types.TIME:
4574: case java.sql.Types.TIMESTAMP:
4575: parseDate[i] = true;
4576: }
4577: }
4578: } catch (SQLException se) {
4579: throw new SqlToolError(rb.getString(
4580: SqltoolRB.QUERY_METADATAFAIL, typeQuerySb
4581: .toString()), se);
4582: }
4583:
4584: for (int i = 0; i < autonulls.length; i++) {
4585: if (i > 0) {
4586: sb.append(", ");
4587: }
4588:
4589: sb.append('?');
4590: }
4591:
4592: // Initialize REJECT file(s)
4593: int rejectCount = 0;
4594: File rejectFile = null;
4595: File rejectReportFile = null;
4596: PrintWriter rejectWriter = null;
4597: PrintWriter rejectReportWriter = null;
4598: if (dsvRejectFile != null)
4599: try {
4600: rejectFile = new File(dsvRejectFile);
4601: rejectWriter = new PrintWriter(
4602: (charset == null) ? (new OutputStreamWriter(
4603: new FileOutputStream(rejectFile)))
4604: : (new OutputStreamWriter(
4605: new FileOutputStream(rejectFile),
4606: charset)));
4607: // Replace with just "(new FileOutputStream(file), charset)"
4608: // once use defaultCharset from Java 1.5 in charset init.
4609: // above.
4610: rejectWriter.print(headerLine + dsvRowDelim);
4611: } catch (IOException ioe) {
4612: throw new SqlToolError(rb.getString(
4613: SqltoolRB.DSV_REJECTFILE_SETUPFAIL,
4614: dsvRejectFile), ioe);
4615: }
4616: if (dsvRejectReport != null)
4617: try {
4618: rejectReportFile = new File(dsvRejectReport);
4619: rejectReportWriter = new PrintWriter(
4620: (charset == null) ? (new OutputStreamWriter(
4621: new FileOutputStream(rejectReportFile)))
4622: : (new OutputStreamWriter(
4623: new FileOutputStream(
4624: rejectReportFile),
4625: charset)));
4626: // Replace with just "(new FileOutputStream(file), charset)"
4627: // once use defaultCharset from Java 1.5 in charset init.
4628: // above.
4629: rejectReportWriter.println(rb.getString(
4630: SqltoolRB.REJECTREPORT_TOP, new String[] {
4631: (new java.util.Date()).toString(),
4632: file.getPath(),
4633: ((rejectFile == null) ? rb
4634: .getString(SqltoolRB.NONE)
4635: : rejectFile.getPath()),
4636: ((rejectFile == null) ? null
4637: : rejectFile.getPath()), }));
4638: } catch (IOException ioe) {
4639: throw new SqlToolError(rb.getString(
4640: SqltoolRB.DSV_REJECTREPORT_SETUPFAIL,
4641: dsvRejectReport), ioe);
4642: }
4643:
4644: int recCount = 0;
4645: int skipCount = 0;
4646: PreparedStatement ps = null;
4647: boolean importAborted = false;
4648:
4649: try {
4650: try {
4651: ps = curConn.prepareStatement(sb.toString() + ')');
4652: } catch (SQLException se) {
4653: throw new SqlToolError(
4654: rb.getString(SqltoolRB.INSERTION_PREPAREFAIL,
4655: sb.toString()), se);
4656: }
4657: String[] dataVals = new String[autonulls.length];
4658: // Length is number of cols to insert INTO, not nec. # in DSV file.
4659: int readColCount;
4660: int storeColCount;
4661: String currentFieldName = null;
4662:
4663: // Insert data rows 1-row-at-a-time
4664: while (true)
4665: try {
4666: try {
4667: recStart = recEnd + dsvRowDelim.length();
4668:
4669: if (recStart >= string.length()) {
4670: break;
4671: }
4672:
4673: recEnd = string.indexOf(dsvRowDelim, recStart);
4674: lineCount++; // Increment when we have line start and end
4675:
4676: if (recEnd < 0) {
4677: // Last record
4678: recEnd = string.length();
4679: }
4680: trimmedLine = string
4681: .substring(recStart, recEnd).trim();
4682: if (trimmedLine.length() < 1) {
4683: continue; // Silently skip blank lines
4684: }
4685: if (skipPrefix != null
4686: && trimmedLine.startsWith(skipPrefix)) {
4687: skipCount++;
4688: continue;
4689: }
4690: if (switching) {
4691: if (trimmedLine.equals("}")) {
4692: switching = false;
4693: continue;
4694: }
4695: int colonAt = trimmedLine.indexOf(':');
4696: if (colonAt < 1
4697: || colonAt == trimmedLine.length() - 1) {
4698: throw new SqlToolError(
4699: rb
4700: .getString(
4701: SqltoolRB.DSV_HEADER_MATCHERNONHEAD,
4702: lineCount));
4703: }
4704: continue;
4705: }
4706:
4707: // Finally we will attempt to add a record!
4708: recCount++;
4709: // Remember that recCount counts both inserts + rejects
4710:
4711: colStart = recStart;
4712: colEnd = -1;
4713: readColCount = 0;
4714: storeColCount = 0;
4715:
4716: while (true) {
4717: if (colEnd == recEnd) {
4718: // We processed final column last time through loop
4719: break;
4720: }
4721:
4722: colEnd = string.indexOf(dsvColDelim,
4723: colStart);
4724:
4725: if (colEnd < 0 || colEnd > recEnd) {
4726: colEnd = recEnd;
4727: }
4728:
4729: if (readColCount == inputColHeadCount) {
4730: throw new RowError(
4731: rb
4732: .getString(
4733: SqltoolRB.DSV_COLCOUNT_MISMATCH,
4734: inputColHeadCount,
4735: 1 + readColCount));
4736: }
4737:
4738: if (headers[readColCount++] != null) {
4739: dataVals[storeColCount++] = string
4740: .substring(colStart, colEnd);
4741: }
4742: colStart = colEnd + dsvColDelim.length();
4743: }
4744: if (readColCount < inputColHeadCount) {
4745: throw new RowError(rb.getString(
4746: SqltoolRB.DSV_COLCOUNT_MISMATCH,
4747: inputColHeadCount, readColCount));
4748: }
4749: /* Already checked for readColCount too high in prev. block */
4750:
4751: if (constColMap != null) {
4752: Iterator it = constColMap.values()
4753: .iterator();
4754: while (it.hasNext()) {
4755: dataVals[storeColCount++] = (String) it
4756: .next();
4757: }
4758: }
4759: if (storeColCount != dataVals.length) {
4760: throw new RowError(rb.getString(
4761: SqltoolRB.DSV_INSERTCOL_MISMATCH,
4762: dataVals.length, storeColCount));
4763: }
4764:
4765: for (int i = 0; i < dataVals.length; i++) {
4766: currentFieldName = insertFieldName[i];
4767: if (autonulls[i])
4768: dataVals[i] = dataVals[i].trim();
4769: // N.b. WE SPECIFICALLY DO NOT HANDLE TIMES WITHOUT
4770: // DATES, LIKE "3:14:00", BECAUSE, WHILE THIS MAY BE
4771: // USEFUL AND EFFICIENT, IT IS NOT PORTABLE.
4772: //System.err.println("ps.setString(" + i + ", "
4773: // + dataVals[i] + ')');
4774:
4775: if (parseDate[i]) {
4776: if ((dataVals[i].length() < 1 && autonulls[i])
4777: || dataVals[i]
4778: .equals(nullRepToken)) {
4779: ps.setTimestamp(i + 1, null);
4780: } else {
4781: dateString = (dataVals[i]
4782: .indexOf(':') > 0) ? dataVals[i]
4783: : (dataVals[i] + " 0:00:00");
4784: // BEWARE: This may not work for some foreign
4785: // date/time formats.
4786: try {
4787: ps
4788: .setTimestamp(
4789: i + 1,
4790: java.sql.Timestamp
4791: .valueOf(dateString));
4792: } catch (IllegalArgumentException iae) {
4793: throw new RowError(
4794: rb
4795: .getString(
4796: SqltoolRB.TIME_BAD,
4797: dateString),
4798: iae);
4799: }
4800: }
4801: } else if (parseBool[i]) {
4802: if ((dataVals[i].length() < 1 && autonulls[i])
4803: || dataVals[i]
4804: .equals(nullRepToken)) {
4805: ps.setNull(i + 1,
4806: java.sql.Types.BOOLEAN);
4807: } else {
4808: try {
4809: ps.setBoolean(i + 1, Boolean
4810: .valueOf(dataVals[i])
4811: .booleanValue());
4812: // Boolean... is equivalent to Java 4's
4813: // Boolean.parseBoolean().
4814: } catch (IllegalArgumentException iae) {
4815: throw new RowError(
4816: rb
4817: .getString(
4818: SqltoolRB.BOOLEAN_BAD,
4819: dataVals[i]),
4820: iae);
4821: }
4822: }
4823: } else {
4824: ps
4825: .setString(
4826: i + 1,
4827: (((dataVals[i].length() < 1 && autonulls[i]) || dataVals[i]
4828: .equals(nullRepToken)) ? null
4829: : dataVals[i]));
4830: }
4831: currentFieldName = null;
4832: }
4833:
4834: retval = ps.executeUpdate();
4835:
4836: if (retval != 1) {
4837: throw new RowError(
4838: rb
4839: .getString(
4840: SqltoolRB.INPUTREC_MODIFIED,
4841: retval));
4842: }
4843:
4844: possiblyUncommitteds.set(true);
4845: } catch (SQLException se) {
4846: throw new RowError(null, se);
4847: }
4848: } catch (RowError re) {
4849: rejectCount++;
4850: if (rejectWriter != null
4851: || rejectReportWriter != null) {
4852: if (rejectWriter != null) {
4853: rejectWriter.print(string.substring(
4854: recStart, recEnd)
4855: + dsvRowDelim);
4856: }
4857: if (rejectReportWriter != null) {
4858: genRejectReportRecord(rejectReportWriter,
4859: rejectCount, lineCount,
4860: currentFieldName, re.getMessage(),
4861: re.getCause());
4862: }
4863: } else {
4864: importAborted = true;
4865: throw new SqlToolError(rb.getString(
4866: SqltoolRB.DSV_RECIN_FAIL, lineCount,
4867: currentFieldName)
4868: + ((re.getMessage() == null) ? ""
4869: : (" " + re.getMessage())), re
4870: .getCause());
4871: }
4872: }
4873: } finally {
4874: String summaryString = null;
4875: if (recCount > 0) {
4876: summaryString = rb
4877: .getString(
4878: SqltoolRB.DSV_IMPORT_SUMMARY,
4879: new String[] {
4880: ((skipPrefix == null) ? ""
4881: : ("'" + skipPrefix + "'-")),
4882: Integer.toString(skipCount),
4883: Integer.toString(rejectCount),
4884: Integer.toString(recCount
4885: - rejectCount),
4886: (importAborted ? "importAborted"
4887: : null) });
4888: stdprintln(summaryString);
4889: }
4890: try {
4891: if (recCount > rejectCount && !curConn.getAutoCommit()) {
4892: stdprintln(rb
4893: .getString(SqltoolRB.INSERTIONS_NOTCOMMITTED));
4894: }
4895: } catch (SQLException se) {
4896: stdprintln(rb.getString(SqltoolRB.AUTOCOMMIT_FETCHFAIL));
4897: stdprintln(rb
4898: .getString(SqltoolRB.INSERTIONS_NOTCOMMITTED));
4899: // No reason to throw here. If use attempts to use the
4900: // connection for anything significant, we will throw then.
4901: }
4902: if (rejectWriter != null) {
4903: rejectWriter.flush();
4904: rejectWriter.close();
4905: }
4906: if (rejectReportWriter != null && rejectCount > 0) {
4907: rejectReportWriter.println(rb.getString(
4908: SqltoolRB.REJECTREPORT_BOTTOM, summaryString,
4909: revnum));
4910: rejectReportWriter.flush();
4911: rejectReportWriter.close();
4912: }
4913: if (rejectCount == 0) {
4914: if (rejectFile != null && rejectFile.exists()
4915: && !rejectFile.delete())
4916: errprintln(rb.getString(
4917: SqltoolRB.DSV_REJECTFILE_PURGEFAIL,
4918: rejectFile.toString()));
4919: if (rejectReportFile != null
4920: && !rejectReportFile.delete())
4921: errprintln(rb.getString(
4922: SqltoolRB.DSV_REJECTREPORT_PURGEFAIL,
4923: (rejectFile == null) ? null : rejectFile
4924: .toString()));
4925: // These are trivial errors.
4926: }
4927: }
4928: }
4929:
4930: public static void appendLine(StringBuffer sb, String s) {
4931: sb.append(s + LS);
4932: }
4933:
4934: /**
4935: * Does a poor-man's parse of a MSDOS command line and parses it
4936: * into a WIndows cmd.exe invocation to approximate.
4937: */
4938: static private String[] genWinArgs(String monolithic) {
4939: List list = new ArrayList();
4940: list.add("cmd.exe");
4941: list.add("/y");
4942: list.add("/c");
4943: Matcher m = wincmdPattern.matcher(monolithic);
4944: String[] internalTokens;
4945: while (m.find()) {
4946: for (int i = 1; i <= m.groupCount(); i++) {
4947: if (m.group(i) == null)
4948: continue;
4949: if (m.group(i).length() > 1
4950: && m.group(i).charAt(0) == '"') {
4951: list.add(m.group(i).substring(1,
4952: m.group(i).length() - 1));
4953: continue;
4954: }
4955: internalTokens = m.group(i).split("\\s+");
4956: for (int j = 0; j < internalTokens.length; j++)
4957: list.add(internalTokens[j]);
4958: }
4959: }
4960: return (String[]) list.toArray(new String[] {});
4961: }
4962:
4963: private void genRejectReportRecord(PrintWriter pw, int rCount,
4964: int lCount, String field, String eMsg, Throwable cause) {
4965: pw
4966: .println(rb
4967: .getString(
4968: SqltoolRB.REJECTREPORT_ROW,
4969: new String[] {
4970: ((rCount % 2 == 0) ? "even"
4971: : "odd")
4972: + "row",
4973: Integer.toString(rCount),
4974: Integer.toString(lCount),
4975: ((field == null) ? " "
4976: : field),
4977: (((eMsg == null) ? "" : eMsg)
4978: + ((eMsg == null || cause == null) ? ""
4979: : "<HR/>") + ((cause == null) ? ""
4980: : ((cause instanceof SQLException && cause
4981: .getMessage() != null) ? cause
4982: .getMessage()
4983: : cause
4984: .toString()))) }));
4985: }
4986: }
|