/* Copyright (c) 2001-2007, The HSQL Development Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the HSQL Development Group nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hsqldb.util;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/* $Id: SqlFile.java,v 1.169 2007/08/02 18:15:32 unsaved Exp $ */
/**
* Encapsulation of a sql text file like 'myscript.sql'.
* The ultimate goal is to run the execute() method to feed the SQL
* commands within the file to a jdbc connection.
*
* Some implementation comments and variable names use keywords based
* on the following definitions. <UL>
* <LI> COMMAND = Statement || SpecialCommand || BufferCommand
* Statement = SQL statement like "SQL Statement;"
* SpecialCommand = Special Command like "\x arg..."
* BufferCommand = Editing/buffer command like ":s/this/that/"
*
* When entering SQL statements, you are always "appending" to the
* "immediate" command (not the "buffer", which is a different thing).
* All you can do to the immediate command is append new lines to it,
* execute it, or save it to buffer.
* When you are entering a buffer edit command like ":s/this/that/",
* your immediate command is the buffer-edit-command. The buffer
* is the command string that you are editing.
* The buffer usually contains either an exact copy of the last command
* executed or sent to buffer by entering a blank line,
* but BUFFER commands can change the contents of the buffer.
*
* In general, the special commands mirror those of Postgresql's psql,
* but SqlFile handles command editing much different from Postgresql
* because of Java's lack of support for raw tty I/O.
* The \p special command, in particular, is very different from psql's.
*
* Buffer commands are unique to SQLFile. The ":" commands allow
* you to edit the buffer and to execute the buffer.
*
* \d commands are very poorly supported for Mysql because
* (a) Mysql lacks most of the most basic JDBC support elements, and
* the most basic role and schema features, and
* (b) to access the Mysql data dictionay, one must change the database
* instance (to do that would require work to restore the original state
* and could have disastrous effects upon transactions).
*
* To make changes to this class less destructive to external callers,
* the input parameters should be moved to setters (probably JavaBean
* setters would be best) instead of constructor args and System
* Properties.
*
* The process*() methods, other than processBuffHist() ALWAYS execute
* on "buffer", and expect it to contain the method specific prefix
* (if any).
*
* @version $Revision: 1.169 $
* @author Blaine Simpson unsaved@users
*/
public class SqlFile {
private static final int DEFAULT_HISTORY_SIZE = 40;
private File file;
private boolean interactive;
private String primaryPrompt = "sql> ";
private String rawPrompt = null;
private String contPrompt = " +> ";
private Connection curConn = null;
private boolean htmlMode = false;
private Map userVars; // Always a non-null map set in cons.
private List history = null;
private int rawMode = RAW_FALSE;
private String nullRepToken = null;
private String dsvTargetFile = null;
private String dsvTargetTable = null;
private String dsvConstCols = null;
private String dsvRejectFile = null;
private String dsvRejectReport = null;
public static String LS = System.getProperty("line.separator");
private int maxHistoryLength = 1;
private SqltoolRB rb = null;
private String magicPrefix = null;
// For append editing, this is automatically prefixed to what the
// user enters.
private static final int RAW_FALSE = 0; // Raw mode off
private static final int RAW_EMPTY = 1; // Raw mode on, but no raw input yet
private static final int RAW_DATA = 2; // Raw mode on and we have input
/**
* N.b. javax.util.regex Optional capture groups (...)? are completely
* unpredictable wrt whether you get a null capture group vs. no capture.
* Must always check count!
*/
private static Pattern specialPattern =
Pattern.compile("\\s*\\\\(\\S+)(?:\\s+(.*\\S))?\\s*");
private static Pattern plPattern =
Pattern.compile("\\s*\\*\\s*(.*\\S)?\\s*");
private static Pattern foreachPattern =
Pattern.compile("\\s*\\*\\s*foreach\\s+(\\S+)\\s*\\(([^)]*)\\)\\s*");
private static Pattern ifwhilePattern =
Pattern.compile("\\s*\\*\\s*\\S+\\s*\\(([^)]*)\\)\\s*");
private static Pattern varsetPattern =
Pattern.compile("\\s*\\*\\s*(\\S+)\\s*([=_~])\\s*(?:(.*\\S)\\s*)?");
private static Pattern substitutionPattern =
Pattern.compile("(\\S)(.+?)\\1(.*?)\\1(.+)?\\s*");
// Note that this pattern does not include the leading ":s".
private static Pattern slashHistoryPattern =
Pattern.compile("\\s*/([^/]+)/\\s*(\\S.*)?");
private static Pattern historyPattern =
Pattern.compile("\\s*(-?\\d+)?\\s*(\\S.*)?");
// Note that this pattern does not include the leading ":".
private static Pattern wincmdPattern = null;
static {
if (System.getProperty("os.name").startsWith("Windows")) {
wincmdPattern = Pattern.compile("([^\"]+)?(\"[^\"]*\")?");
}
}
// This can throw a runtime exception, but since the pattern
// Strings are constant, one test run of the program will tell
// if the patterns are good.
/**
* Encapsulate updating local variables which depend upon PL variables.
*
* Right now this is called whenever the user variable map is changed.
* It would be more efficient to do it JIT by keeping track of when
* the vars may be "dirty" by a variable map change, and having all
* methods that use the settings call a conditional updater, but that
* is less reliable since there is no way to guarantee that the vars
* are not used without checking.
*/
private void updateUserSettings() {
dsvSkipPrefix = SqlFile.convertEscapes(
(String) userVars.get("*DSV_SKIP_PREFIX"));
if (dsvSkipPrefix == null) {
dsvSkipPrefix = DEFAULT_SKIP_PREFIX;
}
dsvSkipCols = (String) userVars.get("*DSV_SKIP_COLS");
dsvColDelim =
SqlFile.convertEscapes((String) userVars.get("*DSV_COL_DELIM"));
if (dsvColDelim == null) {
dsvColDelim =
SqlFile.convertEscapes((String) userVars.get("*CSV_COL_DELIM"));
}
if (dsvColDelim == null) {
dsvColDelim = DEFAULT_COL_DELIM;
}
dsvRowDelim =
SqlFile.convertEscapes((String) userVars.get("*DSV_ROW_DELIM"));
if (dsvRowDelim == null) {
dsvRowDelim =
SqlFile.convertEscapes((String) userVars.get("*CSV_ROW_DELIM"));
}
if (dsvRowDelim == null) {
dsvRowDelim = DEFAULT_ROW_DELIM;
}
dsvTargetFile = (String) userVars.get("*DSV_TARGET_FILE");
if (dsvTargetFile == null) {
dsvTargetFile = (String) userVars.get("*CSV_FILEPATH");
}
dsvTargetTable = (String) userVars.get("*DSV_TARGET_TABLE");
if (dsvTargetTable == null) {
dsvTargetTable = (String) userVars.get("*CSV_TABLENAME");
// This just for legacy variable name.
}
dsvConstCols = (String) userVars.get("*DSV_CONST_COLS");
dsvRejectFile = (String) userVars.get("*DSV_REJECT_FILE");
dsvRejectReport = (String) userVars.get("*DSV_REJECT_REPORT");
nullRepToken = (String) userVars.get("*NULL_REP_TOKEN");
if (nullRepToken == null) {
nullRepToken = (String) userVars.get("*CSV_NULL_REP");
}
if (nullRepToken == null) {
nullRepToken = DEFAULT_NULL_REP;
}
}
/**
* Private class to "share" a variable among a family of SqlFile
* instances.
*/
private static class BooleanBucket {
BooleanBucket() {}
private boolean bPriv = false;
public void set(boolean bIn) {
bPriv = bIn;
}
public boolean get() {
return bPriv;
}
}
BooleanBucket possiblyUncommitteds = new BooleanBucket();
// This is an imperfect solution since when user runs SQL they could
// be running DDL or a commit or rollback statement. All we know is,
// they MAY run some DML that needs to be committed.
private static final String DIVIDER =
"-----------------------------------------------------------------"
+ "-----------------------------------------------------------------";
// Needs to be at least as wide as the widest field or header displayed.
private static final String SPACES =
" "
+ " ";
// Needs to be at least as wide as the widest field or header displayed.
private static String revnum = null;
static {
revnum = "354";
}
private String DSV_OPTIONS_TEXT = null;
private String D_OPTIONS_TEXT = null;
private String RAW_LEADIN_MSG = null;
/**
* Interpret lines of input file as SQL Statements, Comments,
* Special Commands, and Buffer Commands.
* Most Special Commands and many Buffer commands are only for
* interactive use.
*
* @param inFile inFile of null means to read stdin.
* @param inInteractive If true, prompts are printed, the interactive
* Special commands are enabled, and
* continueOnError defaults to true.
* @throws IOException If can't open specified SQL file.
*/
public SqlFile(File inFile, boolean inInteractive, Map inVars)
throws IOException {
// Set up ResourceBundle first, so that any other errors may be
// reported with localized messages.
try {
rb = new SqltoolRB();
rb.validate();
rb.setMissingPosValueBehavior(
ValidatingResourceBundle.NOOP_BEHAVIOR);
rb.setMissingPropertyBehavior(
ValidatingResourceBundle.NOOP_BEHAVIOR);
} catch (RuntimeException re) {
System.err.println("Failed to initialize resource bundle");
throw re;
}
rawPrompt = rb.getString(SqltoolRB.RAWMODE_PROMPT) + "> ";
DSV_OPTIONS_TEXT = rb.getString(SqltoolRB.DSV_OPTIONS);
D_OPTIONS_TEXT = rb.getString(SqltoolRB.D_OPTIONS);
RAW_LEADIN_MSG = rb.getString(SqltoolRB.RAW_LEADIN);
DSV_X_SYNTAX_MSG = rb.getString(SqltoolRB.DSV_X_SYNTAX);
DSV_M_SYNTAX_MSG = rb.getString(SqltoolRB.DSV_M_SYNTAX);
nobufferYetString = rb.getString(SqltoolRB.NOBUFFER_YET);
file = inFile;
interactive = inInteractive;
userVars = inVars;
if (userVars == null) {
userVars = new HashMap();
}
updateUserSettings();
if (file != null &&!file.canRead()) {
throw new IOException(rb.getString(SqltoolRB.SQLFILE_READFAIL,
file.toString()));
}
if (interactive) {
history = new ArrayList();
String histLenString = System.getProperty("sqltool.historyLength");
if (histLenString != null) try {
maxHistoryLength = Integer.parseInt(histLenString);
} catch (Exception e) {
} else {
maxHistoryLength = DEFAULT_HISTORY_SIZE;
}
}
}
/**
* Constructor for reading stdin instead of a file for commands.
*
* @see #SqlFile(File,boolean)
*/
public SqlFile(boolean inInteractive, Map inVars) throws IOException {
this(null, inInteractive, inVars);
}
/**
* Process all the commands on stdin.
*
* @param conn The JDBC connection to use for SQL Commands.
* @see #execute(Connection,PrintStream,PrintStream,boolean)
*/
public void execute(Connection conn,
Boolean coeOverride)
throws SqlToolError, SQLException {
execute(conn, System.out, System.err, coeOverride);
}
/**
* Process all the commands on stdin.
*
* @param conn The JDBC connection to use for SQL Commands.
* @see #execute(Connection,PrintStream,PrintStream,boolean)
*/
public void execute(Connection conn,
boolean coeOverride)
throws SqlToolError, SQLException {
execute(conn, System.out, System.err, new Boolean(coeOverride));
}
// So we can tell how to handle quit and break commands.
public boolean recursed = false;
private String lastSqlStatement = null;
private int curLinenum = -1;
private PrintStream psStd = null;
private PrintStream psErr = null;
private PrintWriter pwQuery = null;
private PrintWriter pwDsv = null;
StringBuffer immCmdSB = new StringBuffer();
private boolean continueOnError = false;
/*
* This is reset upon each execute() invocation (to true if interactive,
* false otherwise).
*/
private static final String DEFAULT_CHARSET = null;
// Change to Charset.defaultCharset().name(); once we can use Java 1.5!
private BufferedReader br = null;
private String charset = null;
private String buffer = null;
/**
* Process all the commands in the file (or stdin) associated with
* "this" object.
* Run SQL in the file through the given database connection.
*
* This is synchronized so that I can use object variables to keep
* track of current line number, command, connection, i/o streams, etc.
*
* Sets encoding character set to that specified with System Property
* 'sqlfile.charset'. Defaults to "US-ASCII".
*
* @param conn The JDBC connection to use for SQL Commands.
* @throws SQLExceptions thrown by JDBC driver.
* Only possible if in "\c false" mode.
* @throws SqlToolError all other errors.
* This includes including QuitNow, BreakException,
* ContinueException for recursive calls only.
*/
public synchronized void execute(Connection conn, PrintStream stdIn,
PrintStream errIn,
Boolean coeOverride)
throws SqlToolError,
SQLException {
psStd = stdIn;
psErr = errIn;
curConn = conn;
curLinenum = -1;
String inputLine;
String trimmedInput;
String deTerminated;
boolean inComment = false; // Gobbling up a comment
int postCommentIndex;
boolean rollbackUncoms = true;
continueOnError = (coeOverride == null) ? interactive
: coeOverride.booleanValue();
if (userVars.size() > 0) {
plMode = true;
}
String specifiedCharSet = System.getProperty("sqlfile.charset");
charset = ((specifiedCharSet == null) ? DEFAULT_CHARSET
: specifiedCharSet);
try {
br = new BufferedReader((charset == null)
? (new InputStreamReader((file == null)
? System.in : (new FileInputStream(file))))
: (new InputStreamReader(((file == null)
? System.in : (new FileInputStream(file))),
charset)));
// Replace with just "(new FileInputStream(file), charset)"
// once use defaultCharset from Java 1.5 in charset init. above.
curLinenum = 0;
if (interactive) {
stdprintln(rb.getString(SqltoolRB.SQLFILE_BANNER, revnum));
}
while (true) {
if (interactive && magicPrefix == null) {
psStd.print((immCmdSB.length() > 0 || rawMode == RAW_DATA)
? contPrompt : ((rawMode == RAW_FALSE)
? primaryPrompt : rawPrompt));
}
inputLine = br.readLine();
if (magicPrefix != null) {
inputLine = magicPrefix + inputLine;
magicPrefix = null;
}
if (inputLine == null) {
/*
* This is because interactive EOD on some OSes doesn't
* send a line-break, resulting in no linebreak at all
* after the SqlFile prompt or whatever happens to be
* on their screen.
*/
if (interactive) {
psStd.println();
}
break;
}
curLinenum++;
if (inComment) {
postCommentIndex = inputLine.indexOf("*/") + 2;
if (postCommentIndex > 1) {
// I see no reason to leave comments in history.
inputLine = inputLine.substring(postCommentIndex);
// Empty the buffer. The non-comment remainder of
// this line is either the beginning of a new SQL
// or Special command, or an empty line.
immCmdSB.setLength(0);
inComment = false;
} else {
// Just completely ignore the input line.
continue;
}
}
trimmedInput = inputLine.trim();
try {
if (rawMode != RAW_FALSE) {
boolean rawExecute = inputLine.equals(".;");
if (rawExecute || inputLine.equals(":.")) {
if (rawMode == RAW_EMPTY) {
rawMode = RAW_FALSE;
throw new SqlToolError(
rb.getString(SqltoolRB.RAW_EMPTY));
}
rawMode = RAW_FALSE;
setBuf(immCmdSB.toString());
immCmdSB.setLength(0);
if (rawExecute) {
historize();
processSQL();
} else if (interactive) {
stdprintln(rb.getString(
SqltoolRB.RAW_MOVEDTOBUFFER));
}
} else {
if (rawMode == RAW_DATA) {
immCmdSB.append('\n');
}
rawMode = RAW_DATA;
if (inputLine.length() > 0) {
immCmdSB.append(inputLine);
}
}
continue;
}
if (immCmdSB.length() == 0) {
// NEW Immediate Command (i.e., not appending).
if (trimmedInput.startsWith("/*")) {
postCommentIndex = trimmedInput.indexOf("*/", 2)
+ 2;
if (postCommentIndex > 1) {
// I see no reason to leave comments in
// history.
inputLine = inputLine.substring(
postCommentIndex + inputLine.length()
- trimmedInput.length());
trimmedInput = inputLine.trim();
} else {
// Just so we get continuation lines:
immCmdSB.append("COMMENT");
inComment = true;
continue;
}
}
if (trimmedInput.length() == 0) {
// This is just to filter out useless newlines at
// beginning of commands.
continue;
}
if ((trimmedInput.charAt(0) == '*'
&& (trimmedInput.length() < 2
|| trimmedInput.charAt(1) != '{'))
|| trimmedInput.charAt(0) == '\\') {
setBuf(trimmedInput);
processFromBuffer();
continue;
}
if (trimmedInput.charAt(0) == ':' && interactive) {
processBuffHist(trimmedInput.substring(1));
continue;
}
String ucased = trimmedInput.toUpperCase();
if (ucased.startsWith("DECLARE")
|| ucased.startsWith("BEGIN")) {
rawMode = RAW_EMPTY;
immCmdSB.append(inputLine);
if (interactive) {
stdprintln(RAW_LEADIN_MSG);
}
continue;
}
}
if (trimmedInput.length() == 0 && interactive &&!inComment) {
// Blank lines delimit commands ONLY IN INTERACTIVE
// MODE!
setBuf(immCmdSB.toString());
immCmdSB.setLength(0);
stdprintln(rb.getString(SqltoolRB.INPUT_MOVEDTOBUFFER));
continue;
}
deTerminated = SqlFile.deTerminated(inputLine);
// A null terminal line (i.e., /\s*;\s*$/) is never useful.
if (!trimmedInput.equals(";")) {
if (immCmdSB.length() > 0) {
immCmdSB.append('\n');
}
immCmdSB.append((deTerminated == null) ? inputLine
: deTerminated);
}
if (deTerminated == null) {
continue;
}
// If we reach here, then immCmdSB contains a complete
// SQL command.
if (immCmdSB.toString().trim().length() == 0) {
immCmdSB.setLength(0);
throw new SqlToolError(rb.getString(
SqltoolRB.SQLSTATEMENT_EMPTY));
// There is nothing inherently wrong with issuing
// an empty command, like to test DB server health.
// But, this check effectively catches many syntax
// errors early, and the DB check can be done by
// sending a comment like "// comment".
}
setBuf(immCmdSB.toString());
immCmdSB.setLength(0);
historize();
processSQL();
} catch (BadSpecial bs) {
// BadSpecials ALWAYS have non-null getMessage().
errprintln(rb.getString(SqltoolRB.ERRORAT,
new String[] {
((file == null) ? "stdin" : file.toString()),
Integer.toString(curLinenum),
inputLine,
bs.getMessage(),
}
));
Throwable cause = bs.getCause();
if (cause != null) {
errprintln(rb.getString(SqltoolRB.CAUSEREPORT,
cause.toString()));
}
if (!continueOnError) {
throw new SqlToolError(bs);
}
} catch (SQLException se) {
errprintln("SQL " + rb.getString(SqltoolRB.ERRORAT,
new String[] {
((file == null) ? "stdin" : file.toString()),
Integer.toString(curLinenum),
lastSqlStatement,
se.getMessage(),
}));
// It's possible that we could have
// SQLException.getMessage() == null, but if so, I think
// it reasonsable to show "null". That's a DB inadequacy.
if (!continueOnError) {
throw se;
}
} catch (BreakException be) {
String msg = be.getMessage();
if (recursed) {
rollbackUncoms = false;
// Recursion level will exit by rethrowing the BE.
// We set rollbackUncoms to false because only the
// top level should detect break errors and
// possibly roll back.
} else if (msg == null || msg.equals("file")) {
break;
} else {
errprintln(rb.getString(SqltoolRB.BREAK_UNSATISFIED,
msg));
}
if (recursed ||!continueOnError) {
throw be;
}
} catch (ContinueException ce) {
String msg = ce.getMessage();
if (recursed) {
rollbackUncoms = false;
} else {
errprintln(rb.getString(SqltoolRB.CONTINUE_UNSATISFIED,
msg));
}
if (recursed ||!continueOnError) {
throw ce;
}
} catch (QuitNow qn) {
throw qn;
} catch (SqlToolError ste) {
errprint(rb.getString(SqltoolRB.ERRORAT,
new String[] {
((file == null) ? "stdin" : file.toString()),
Integer.toString(curLinenum),
inputLine,
((ste.getMessage() == null)
? "" : ste.getMessage())
}
));
if (ste.getMessage() != null) errprintln("");
Throwable cause = ste.getCause();
if (cause != null) {
errprintln(rb.getString(SqltoolRB.CAUSEREPORT,
cause.toString()));
}
if (!continueOnError) {
throw ste;
}
}
immCmdSB.setLength(0);
}
if (inComment || immCmdSB.length() != 0) {
errprintln(rb.getString(SqltoolRB.INPUT_UNTERMINATED,
immCmdSB.toString()));
throw new SqlToolError(rb.getString(
SqltoolRB.INPUT_UNTERMINATED, immCmdSB.toString()));
}
rollbackUncoms = false;
// Exiting gracefully, so don't roll back.
} catch (IOException ioe) {
throw new SqlToolError(rb.getString(
SqltoolRB.PRIMARYINPUT_ACCESSFAIL), ioe);
} catch (QuitNow qn) {
if (recursed) {
throw qn;
// Will rollback if conditions otherwise require.
// Otherwise top level will decide based upon qn.getMessage().
}
rollbackUncoms = (qn.getMessage() != null);
if (rollbackUncoms) {
errprintln(rb.getString(SqltoolRB.ABORTING, qn.getMessage()));
throw new SqlToolError(qn.getMessage());
}
return;
} finally {
closeQueryOutputStream();
if (fetchingVar != null) {
errprintln(rb.getString(SqltoolRB.PLVAR_SET_INCOMPLETE,
fetchingVar));
rollbackUncoms = true;
}
if (br != null) try {
br.close();
} catch (IOException ioe) {
throw new SqlToolError(rb.getString(
SqltoolRB.INPUTREADER_CLOSEFAIL), ioe);
}
if (rollbackUncoms && possiblyUncommitteds.get()) {
errprintln(rb.getString(SqltoolRB.ROLLINGBACK));
curConn.rollback();
possiblyUncommitteds.set(false);
}
}
}
/**
* Returns a copy of given string without a terminating semicolon.
* If there is no terminating semicolon, null is returned.
*
* @param inString Base String, which will not be modified (because
* a "copy" will be returned).
* @returns Null if inString contains no terminating semi-colon.
*/
private static String deTerminated(String inString) {
int index = inString.lastIndexOf(';');
if (index < 0) {
return null;
}
for (int i = index + 1; i < inString.length(); i++) {
if (!Character.isWhitespace(inString.charAt(i))) {
return null;
}
}
return inString.substring(0, index);
}
/**
* Utility nested Exception class for internal use only.
*
* Do not instantiate with null message.
*/
static private class BadSpecial extends AppendableException {
static final long serialVersionUID = 7162440064026570590L;
BadSpecial(String s) {
super(s);
if (s == null)
throw new RuntimeException(
"Must construct BadSpecials with non-null message");
}
BadSpecial(String s, Throwable t) {
super(s, t);
if (s == null)
throw new RuntimeException(
"Must construct BadSpecials with non-null message");
}
}
/**
* Utility nested Exception class for internal use.
* This must extend SqlToolError because it has to percolate up from
* recursions of SqlTool.execute(), yet SqlTool.execute() is public.
* Therefore, external users have no reason to specifically handle
* QuitNow.
*/
private class QuitNow extends SqlToolError {
static final long serialVersionUID = 1811094258670900488L;
public QuitNow(String s) {
super(s);
}
public QuitNow() {
super();
}
}
/**
* Utility nested Exception class for internal use.
* This must extend SqlToolError because it has to percolate up from
* recursions of SqlTool.execute(), yet SqlTool.execute() is public.
* Therefore, external users have no reason to specifically handle
* BreakException.
*/
private class BreakException extends SqlToolError {
static final long serialVersionUID = 351150072817675994L;
public BreakException() {
super();
}
public BreakException(String s) {
super(s);
}
}
/**
* Utility nested Exception class for internal use.
* This must extend SqlToolError because it has to percolate up from
* recursions of SqlTool.execute(), yet SqlTool.execute() is public.
* Therefore, external users have no reason to specifically handle
* ContinueException.
*/
private class ContinueException extends SqlToolError {
static final long serialVersionUID = 5064604160827106014L;
public ContinueException() {
super();
}
public ContinueException(String s) {
super(s);
}
}
/**
* Utility nested Exception class for internal use only.
*/
private class BadSubst extends Exception {
static final long serialVersionUID = 7325933736897253269L;
BadSubst(String s) {
super(s);
}
}
/**
* Utility nested Exception class for internal use only.
*/
private class RowError extends AppendableException {
static final long serialVersionUID = 754346434606022750L;
RowError(String s) {
super(s);
}
RowError(Throwable t) {
this(null, t);
}
RowError(String s, Throwable t) {
super(s, t);
}
}
/**
* Execute processSql/processPL/processSpecial from buffer.
*/
public void processFromBuffer()
throws BadSpecial, SQLException, SqlToolError {
historize();
if (buffer.charAt(0) == '*' && (buffer.length() < 2
|| buffer.charAt(1) != '{')) {
// Test above just means commands starting with *, EXCEPT
// for commands beginning with *{.
processPL(buffer);
return;
}
if (buffer.charAt(0) == '\\') {
processSpecial(buffer);
return;
}
processSQL();
}
/**
* Process a Buffer/History Command.
*
* Due to the nature of the goal here, we don't trim() "other" like
* we do for other kinds of commands.
*
* @param inString Complete command, less the leading ':' character.
* @throws SQLException thrown by JDBC driver.
* @throws BadSpecial special-command-specific errors.
* @throws SqlToolError all other errors.
*/
private void processBuffHist(String inString)
throws BadSpecial, SQLException, SqlToolError {
if (inString.length() < 1) {
throw new BadSpecial(rb.getString(SqltoolRB.BUFHIST_UNSPECIFIED));
}
// First handle the simple cases where user may not specify a
// command number.
char commandChar = inString.charAt(0);
String other = inString.substring(1);
if (other.trim().length() == 0) {
other = null;
}
switch (commandChar) {
case 'l' :
case 'b' :
enforce1charBH(other, 'l');
if (buffer == null) {
stdprintln(nobufferYetString);
} else {
stdprintln(rb.getString(SqltoolRB.EDITBUFFER_CONTENTS,
buffer));
}
return;
case 'h' :
enforce1charBH(other, 'h');
showHistory();
return;
case '?' :
stdprintln(rb.getString(SqltoolRB.BUFFER_HELP));
return;
}
Integer histNum = null;
Matcher hm = slashHistoryPattern.matcher(inString);
if (hm.matches()) {
histNum = historySearch(hm.group(1));
if (histNum == null) {
stdprintln(rb.getString(SqltoolRB.SUBSTITUTION_NOMATCH));
return;
}
} else {
hm = historyPattern.matcher(inString);
if (!hm.matches()) {
throw new BadSpecial(rb.getString(SqltoolRB.EDIT_MALFORMAT));
// Empirically, I find that his pattern always captures two
// groups. Unfortunately, there's no way to guarantee that :( .
}
histNum = ((hm.group(1) == null || hm.group(1).length() < 1)
? null : new Integer(hm.group(1)));
}
if (hm.groupCount() != 2) {
throw new BadSpecial(rb.getString(SqltoolRB.EDIT_MALFORMAT));
// Empirically, I find that his pattern always captures two
// groups. Unfortunately, there's no way to guarantee that :( .
}
commandChar = ((hm.group(2) == null || hm.group(2).length() < 1)
? '\0' : hm.group(2).charAt(0));
other = ((commandChar == '\0') ? null : hm.group(2).substring(1));
if (other != null && other.length() < 1) other = null;
String targetCommand = ((histNum == null)
? null : commandFromHistory(histNum.intValue()));
// Every command below depends upon buffer content.
switch (commandChar) {
case '\0' : // Special token set above. Just history recall.
setBuf(targetCommand);
stdprintln(rb.getString(SqltoolRB.BUFFER_RESTORED, buffer));
return;
case ';' :
enforce1charBH(other, ';');
if (targetCommand != null) setBuf(targetCommand);
if (buffer == null) throw new BadSpecial(
rb.getString(SqltoolRB.NOBUFFER_YET));
stdprintln(rb.getString(SqltoolRB.BUFFER_EXECUTING, buffer));
processFromBuffer();
return;
case 'a' :
if (targetCommand == null) targetCommand = buffer;
if (targetCommand == null) throw new BadSpecial(
rb.getString(SqltoolRB.NOBUFFER_YET));
immCmdSB.append(targetCommand);
if (other != null) {
String deTerminated = SqlFile.deTerminated(other);
if (!other.equals(";")) {
immCmdSB.append(((deTerminated == null)
? other : deTerminated));
}
if (deTerminated != null) {
// If we reach here, then immCmdSB contains a
// complete command.
setBuf(immCmdSB.toString());
immCmdSB.setLength(0);
stdprintln(rb.getString(SqltoolRB.BUFFER_EXECUTING,
buffer));
processFromBuffer();
return;
}
}
magicPrefix = immCmdSB.toString();
immCmdSB.setLength(0);
if (interactive) stdprint(magicPrefix);
return;
case 'w' :
if (targetCommand == null) targetCommand = buffer;
if (targetCommand == null) throw new BadSpecial(
rb.getString(SqltoolRB.NOBUFFER_YET));
if (other == null) {
throw new BadSpecial(rb.getString(
SqltoolRB.DESTFILE_DEMAND));
}
String targetFile = dereference(other.trim(), false);
// Dereference and trim the target file name
// This is the only case where we dereference a : command.
PrintWriter pw = null;
try {
pw = new PrintWriter((charset == null)
? (new OutputStreamWriter(
new FileOutputStream(targetFile, true)))
: (new OutputStreamWriter(
new FileOutputStream(targetFile, true),
charset))
// Appendmode so can append to an SQL script.
);
// Replace with just "(new FileOutputStream(file), charset)"
// once use defaultCharset from Java 1.5 in charset init.
// above.
pw.print(targetCommand);
if (!targetCommand.matches("\\s*[*:\\\\].*")) pw.print(';');
pw.println();
pw.flush();
} catch (Exception e) {
throw new BadSpecial(rb.getString(SqltoolRB.FILE_APPENDFAIL,
targetFile), e);
} finally {
if (pw != null) pw.close();
}
return;
case 's' :
boolean modeExecute = false;
boolean modeGlobal = false;
if (targetCommand == null) targetCommand = buffer;
if (targetCommand == null) throw new BadSpecial(
rb.getString(SqltoolRB.NOBUFFER_YET));
try {
if (other == null || other.length() < 3) {
throw new BadSubst(rb.getString(
SqltoolRB.SUBSTITUTION_MALFORMAT));
}
Matcher m = substitutionPattern.matcher(other);
if (!m.matches()) {
throw new BadSubst(rb.getString(
SqltoolRB.SUBSTITUTION_MALFORMAT));
}
// Note that this pattern does not include the leading :.
if (m.groupCount() < 3 || m.groupCount() > 4) {
// Assertion failed
throw new RuntimeException(
"Matched substitution pattern, but "
+ "captured " + m.groupCount() + " groups");
}
String optionGroup = (
(m.groupCount() > 3 && m.group(4) != null)
? (new String(m.group(4))) : null);
if (optionGroup != null) {
if (optionGroup.indexOf(';') > -1) {
modeExecute = true;
optionGroup = optionGroup.replaceFirst(";", "");
}
if (optionGroup.indexOf('g') > -1) {
modeGlobal = true;
optionGroup = optionGroup.replaceFirst("g", "");
}
}
Matcher bufferMatcher = Pattern.compile("(?s"
+ ((optionGroup == null) ? "" : optionGroup)
+ ')' + m.group(2)).matcher(targetCommand);
String newBuffer = (modeGlobal
? bufferMatcher.replaceAll(m.group(3))
: bufferMatcher.replaceFirst(m.group(3)));
if (newBuffer.equals(targetCommand)) {
stdprintln(rb.getString(
SqltoolRB.SUBSTITUTION_NOMATCH));
return;
}
setBuf(newBuffer);
stdprintln(rb.getString((modeExecute
? SqltoolRB.BUFFER_EXECUTING
: SqltoolRB.EDITBUFFER_CONTENTS), buffer));
} catch (PatternSyntaxException pse) {
throw new BadSpecial(
rb.getString(SqltoolRB.SUBSTITUTION_SYNTAX), pse);
} catch (BadSubst badswitch) {
throw new BadSpecial(
rb.getString(SqltoolRB.SUBSTITUTION_SYNTAX));
}
if (modeExecute) {
immCmdSB.setLength(0);
processFromBuffer();
}
return;
}
throw new BadSpecial(rb.getString(SqltoolRB.BUFFER_UNKNOWN,
Character.toString(commandChar)));
}
private boolean doPrepare = false;
private String prepareVar = null;
private String dsvColDelim = null;
private String dsvSkipPrefix = null;
private String dsvRowDelim = null;
private String dsvSkipCols = null;
private String DSV_X_SYNTAX_MSG = null;
private String DSV_M_SYNTAX_MSG = null;
private String nobufferYetString = null;
private void enforce1charSpecial(String token, char command)
throws BadSpecial {
if (token.length() != 1) {
throw new BadSpecial(rb.getString(SqltoolRB.SPECIAL_EXTRACHARS,
Character.toString(command), token.substring(1)));
}
}
private void enforce1charBH(String token, char command)
throws BadSpecial {
if (token != null) {
throw new BadSpecial(rb.getString(SqltoolRB.BUFFER_EXTRACHARS,
Character.toString(command), token));
}
}
/**
* Process a Special Command.
*
* @param inString TRIMMED complete command, including the leading
* '\' character.
* @throws SQLException thrown by JDBC driver.
* @throws BadSpecial special-command-specific errors.
* @throws SqlToolError all other errors, plus QuitNow,
* BreakException, ContinueException.
*/
private void processSpecial(String inString)
throws BadSpecial, QuitNow, SQLException, SqlToolError {
if (inString.equals("\\")) {
throw new BadSpecial(rb.getString(SqltoolRB.SPECIAL_UNSPECIFIED));
}
Matcher m = specialPattern.matcher(
plMode ? dereference(inString, false) : inString);
if (!m.matches()) {
throw new BadSpecial(rb.getString(SqltoolRB.SPECIAL_MALFORMAT));
// I think it's impossible to get here, since the pattern is
// so liberal.
}
if (m.groupCount() < 1 || m.groupCount() > 2) {
// Failed assertion
throw new RuntimeException(
"Pattern matched, yet captured " + m.groupCount()
+ " groups");
}
String arg1 = m.group(1);
String other = ((m.groupCount() > 1) ? m.group(2) : null);
switch (arg1.charAt(0)) {
case 'q' :
enforce1charSpecial(arg1, 'q');
if (other != null) {
throw new QuitNow(other);
}
throw new QuitNow();
case 'H' :
enforce1charSpecial(arg1, 'H');
htmlMode = !htmlMode;
stdprintln(rb.getString(SqltoolRB.HTML_MODE,
Boolean.toString(htmlMode)));
return;
case 'm' :
if (arg1.equals("m?") ||
(arg1.equals("m") && other != null
&& other.equals("?"))) {
stdprintln(DSV_OPTIONS_TEXT + LS + DSV_M_SYNTAX_MSG);
return;
}
if (arg1.length() != 1 || other == null) {
throw new BadSpecial(DSV_M_SYNTAX_MSG);
}
boolean noComments = other.charAt(other.length() - 1) == '*';
String skipPrefix = null;
if (noComments) {
other = other.substring(0, other.length()-1).trim();
if (other.length() < 1) {
throw new BadSpecial(DSV_M_SYNTAX_MSG);
}
} else {
skipPrefix = dsvSkipPrefix;
}
int colonIndex = other.indexOf(" :");
if (colonIndex > -1 && colonIndex < other.length() - 2) {
skipPrefix = other.substring(colonIndex + 2);
other = other.substring(0, colonIndex).trim();
}
importDsv(other, skipPrefix);
return;
case 'x' :
if (arg1.equals("x?") ||
(arg1.equals("x") && other != null
&& other.equals("?"))) {
stdprintln(DSV_OPTIONS_TEXT + LS + DSV_X_SYNTAX_MSG);
return;
}
try {
if (arg1.length() != 1 || other == null) {
throw new BadSpecial(DSV_X_SYNTAX_MSG);
}
String tableName = ((other.indexOf(' ') > 0) ? null
: other);
if (dsvTargetFile == null && tableName == null) {
throw new BadSpecial(rb.getString(
SqltoolRB.DSV_TARGETFILE_DEMAND));
}
File dsvFile = new File((dsvTargetFile == null)
? (tableName + ".dsv")
: dsvTargetFile);
pwDsv = new PrintWriter((charset == null)
? (new OutputStreamWriter(new FileOutputStream(dsvFile)))
: (new OutputStreamWriter(new FileOutputStream(dsvFile),
charset)));
// Replace with just "(new FileOutputStream(file), charset)"
// once use defaultCharset from Java 1.5 in charset init.
// above.
ResultSet rs = curConn.createStatement().executeQuery(
(tableName == null) ? other
: ("SELECT * FROM "
+ tableName));
List colList = new ArrayList();
int[] incCols = null;
if (dsvSkipCols != null) {
Set skipCols = new HashSet();
String[] skipColsArray = dsvSkipCols.split("\\s*\\Q"
+ dsvColDelim + "\\E\\s*");
for (int i = 0; i < skipColsArray.length; i++) {
skipCols.add(skipColsArray[i].toLowerCase());
}
ResultSetMetaData rsmd = rs.getMetaData();
for (int i = 1; i <= rsmd.getColumnCount(); i++) {
if (!skipCols.remove(rsmd.getColumnName(i)
.toLowerCase())) {
colList.add(new Integer(i));
}
}
if (colList.size() < 1) {
throw new BadSpecial(rb.getString(
SqltoolRB.DSV_NOCOLSLEFT, dsvSkipCols));
}
if (skipCols.size() > 0) {
throw new BadSpecial(rb.getString(
SqltoolRB.DSV_SKIPCOLS_MISSING,
skipCols.toString()));
}
incCols = new int[colList.size()];
for (int i = 0; i < incCols.length; i++) {
incCols[i] = ((Integer) colList.get(i)).intValue();
}
}
displayResultSet(null, rs, incCols, null);
pwDsv.flush();
stdprintln(rb.getString(SqltoolRB.FILE_WROTECHARS,
Long.toString(dsvFile.length()),
dsvFile.toString()));
} catch (FileNotFoundException e) {
throw new BadSpecial(rb.getString(SqltoolRB.FILE_WRITEFAIL,
other), e);
} catch (UnsupportedEncodingException e) {
throw new BadSpecial(rb.getString(SqltoolRB.FILE_WRITEFAIL,
other), e);
} finally {
// Reset all state changes
if (pwDsv != null) {
pwDsv.close();
}
pwDsv = null;
}
return;
case 'd' :
if (arg1.equals("d?") ||
(arg1.equals("d") && other != null
&& other.equals("?"))) {
stdprintln(D_OPTIONS_TEXT);
return;
}
if (arg1.length() == 2) {
listTables(arg1.charAt(1), other);
return;
}
if (arg1.length() == 1 && other != null) try {
int space = other.indexOf(' ');
if (space < 0) {
describe(other, null);
} else {
describe(other.substring(0, space),
other.substring(space + 1).trim());
}
return;
} catch (SQLException se) {
throw new BadSpecial(rb.getString(
SqltoolRB.METADATA_FETCH_FAIL), se);
}
throw new BadSpecial(rb.getString(SqltoolRB.SPECIAL_D_LIKE));
case 'o' :
enforce1charSpecial(arg1, 'o');
if (other == null) {
if (pwQuery == null) {
throw new BadSpecial(rb.getString(
SqltoolRB.OUTPUTFILE_NONETOCLOSE));
}
closeQueryOutputStream();
return;
}
if (pwQuery != null) {
stdprintln(rb.getString(SqltoolRB.OUTPUTFILE_REOPENING));
closeQueryOutputStream();
}
try {
pwQuery = new PrintWriter((charset == null)
? (new OutputStreamWriter(
new FileOutputStream(other, true)))
: (new OutputStreamWriter(
new FileOutputStream(other, true), charset))
);
// Replace with just "(new FileOutputStream(file), charset)"
// once use defaultCharset from Java 1.5 in charset init.
// above.
/* Opening in append mode, so it's possible that we will
* be adding superfluous <HTML> and <BODY> tags.
* I think that browsers can handle that */
pwQuery.println((htmlMode
? ("<HTML>" + LS + "<!--")
: "#") + " " + (new java.util.Date()) + ". "
+ rb.getString(SqltoolRB.OUTPUTFILE_HEADER,
getClass().getName())
+ (htmlMode ? (" -->" + LS + LS + "<BODY>")
: LS));
pwQuery.flush();
} catch (Exception e) {
throw new BadSpecial(rb.getString(SqltoolRB.FILE_WRITEFAIL,
other), e);
}
return;
case 'i' :
enforce1charSpecial(arg1, 'i');
if (other == null) {
throw new BadSpecial(rb.getString(
SqltoolRB.SQLFILE_NAME_DEMAND));
}
try {
SqlFile sf = new SqlFile(new File(other), false, userVars);
sf.recursed = true;
// Share the possiblyUncommitted state
sf.possiblyUncommitteds = possiblyUncommitteds;
sf.plMode = plMode;
sf.execute(curConn, continueOnError);
} catch (ContinueException ce) {
throw ce;
} catch (BreakException be) {
String beMessage = be.getMessage();
// Handle "file" and plain breaks (by doing nothing)
if (beMessage != null &&!beMessage.equals("file")) {
throw be;
}
} catch (QuitNow qn) {
throw qn;
} catch (Exception e) {
throw new BadSpecial(rb.getString(
SqltoolRB.SQLFILE_EXECUTE_FAIL, other), e);
}
return;
case 'p' :
enforce1charSpecial(arg1, 'p');
if (other == null) {
stdprintln(true);
} else {
stdprintln(other, true);
}
return;
case 'a' :
enforce1charSpecial(arg1, 'a');
if (other != null) {
curConn.setAutoCommit(
Boolean.valueOf(other).booleanValue());
}
stdprintln(rb.getString(SqltoolRB.A_SETTING,
Boolean.toString(curConn.getAutoCommit())));
return;
case '=' :
enforce1charSpecial(arg1, '=');
curConn.commit();
possiblyUncommitteds.set(false);
stdprintln(rb.getString(SqltoolRB.COMMITTED));
return;
case 'b' :
if (arg1.length() == 1) {
if (other != null) {
throw new BadSpecial(rb.getString(
SqltoolRB.SPECIAL_B_MALFORMAT));
}
fetchBinary = true;
return;
}
if (arg1.charAt(1) == 'p') {
if (other != null) {
throw new BadSpecial(rb.getString(
SqltoolRB.SPECIAL_B_MALFORMAT));
}
doPrepare = true;
return;
}
if ((arg1.charAt(1) != 'd' && arg1.charAt(1) != 'l')
|| other == null) {
throw new BadSpecial(rb.getString(
SqltoolRB.SPECIAL_B_MALFORMAT));
}
File file = new File(other);
try {
if (arg1.charAt(1) == 'd') {
dump(file);
} else {
binBuffer = SqlFile.loadBinary(file);
stdprintln(rb.getString(
SqltoolRB.BINARY_LOADEDBYTESINTO,
binBuffer.length));
}
} catch (BadSpecial bs) {
throw bs;
} catch (IOException ioe) {
throw new BadSpecial(rb.getString(SqltoolRB.BINARY_FILEFAIL,
other), ioe);
}
return;
case '*' :
case 'c' :
enforce1charSpecial(arg1, '=');
if (other != null) {
// But remember that we have to abort on some I/O errors.
continueOnError = Boolean.valueOf(other).booleanValue();
}
stdprintln(rb.getString(SqltoolRB.C_SETTING,
Boolean.toString(continueOnError)));
return;
case '?' :
stdprintln(rb.getString(SqltoolRB.SPECIAL_HELP));
return;
case '!' :
// N.b. This DOES NOT HANDLE UNIX shell wildcards, since there
// is no UNIX shell involved.
// Doesn't make sense to incur overhead of a shell without
// stdin capability.
// Can't provide stdin to the executed program because
// the forked program could gobble up program input,
// depending on how SqlTool was invoked, nested scripts,
// etc.
// I'd like to execute the user's default shell if they
// ran "\!" with no argument, but (a) there is no portable
// way to determine the user's default or login shell; and
// (b) shell is useless without stdin ability.
InputStream stream;
byte[] ba = new byte[1024];
String extCommand = ((arg1.length() == 1)
? "" : arg1.substring(1))
+ ((arg1.length() > 1 && other != null)
? " " : "") + ((other == null) ? "" : other);
if (extCommand.trim().length() < 1)
throw new BadSpecial(rb.getString(
SqltoolRB.BANG_INCOMPLETE));
try {
Runtime runtime = Runtime.getRuntime();
Process proc = ((wincmdPattern == null)
? runtime.exec(extCommand)
: runtime.exec(genWinArgs(extCommand))
);
proc.getOutputStream().close();
int i;
stream = proc.getInputStream();
while ((i = stream.read(ba)) > 0) {
stdprint(new String(ba, 0, i));
}
stream.close();
stream = proc.getErrorStream();
while ((i = stream.read(ba)) > 0) {
errprint(new String(ba, 0, i));
}
stream.close();
if (proc.waitFor() != 0) {
throw new BadSpecial(rb.getString(
SqltoolRB.BANG_COMMAND_FAIL, extCommand));
}
} catch (BadSpecial bs) {
throw bs;
} catch (Exception e) {
throw new BadSpecial(rb.getString(
SqltoolRB.BANG_COMMAND_FAIL, extCommand), e);
}
return;
case '.' :
enforce1charSpecial(arg1, '.');
rawMode = RAW_EMPTY;
if (interactive) {
stdprintln(RAW_LEADIN_MSG);
}
return;
}
throw new BadSpecial(rb.getString(SqltoolRB.SPECIAL_UNKNOWN,
Character.toString(arg1.charAt(0))));
}
private static final char[] nonVarChars = {
' ', '\t', '=', '}', '\n', '\r'
};
/**
* Returns index specifying 1 past end of a variable name.
*
* @param inString String containing a variable name
* @param startIndex Index within inString where the variable name begins
* @returns Index within inString, 1 past end of the variable name
*/
static int pastName(String inString, int startIndex) {
String workString = inString.substring(startIndex);
int e = inString.length(); // Index 1 past end of var name.
int nonVarIndex;
for (int i = 0; i < nonVarChars.length; i++) {
nonVarIndex = workString.indexOf(nonVarChars[i]);
if (nonVarIndex > -1 && nonVarIndex < e) {
e = nonVarIndex;
}
}
return startIndex + e;
}
/**
* Deference *{} PL variables and ${} System Property variables.
*
* @throws SqlToolError
*/
private String dereference(String inString,
boolean permitAlias) throws SqlToolError {
/* TODO: Rewrite using java.util.regex. */
String varName, varValue;
StringBuffer expandBuffer = new StringBuffer(inString);
int b, e; // begin and end of name. end really 1 PAST name
int iterations;
if (permitAlias && inString.trim().charAt(0) == '/') {
int slashIndex = inString.indexOf('/');
e = SqlFile.pastName(inString.substring(slashIndex + 1), 0);
// In this case, e is the exact length of the var name.
if (e < 1) {
throw new SqlToolError(rb.getString(
SqltoolRB.PLALIAS_MALFORMAT));
}
varName = inString.substring(slashIndex + 1, slashIndex + 1 + e);
varValue = (String) userVars.get(varName);
if (varValue == null) {
throw new SqlToolError(rb.getString(
SqltoolRB.PLVAR_UNDEFINED, varName));
}
expandBuffer.replace(slashIndex, slashIndex + 1 + e,
(String) userVars.get(varName));
}
String s;
boolean permitUnset;
// Permit unset with: ${:varname}
// Prohibit unset with : ${varnam}
iterations = 0;
while (true) {
s = expandBuffer.toString();
b = s.indexOf("${");
if (b < 0) {
// No more unexpanded variable uses
break;
}
e = s.indexOf('}', b + 2);
if (e == b + 2) {
throw new SqlToolError(rb.getString(SqltoolRB.SYSPROP_EMPTY));
}
if (e < 0) {
throw new SqlToolError(rb.getString(
SqltoolRB.SYSPROP_UNTERMINATED));
}
permitUnset = (s.charAt(b + 2) == ':');
varName = s.substring(b + (permitUnset ? 3 : 2), e);
if (iterations++ > 10000)
throw new SqlToolError(rb.getString(SqltoolRB.VAR_INFINITE,
varName));
varValue = System.getProperty(varName);
if (varValue == null) {
if (permitUnset) {
varValue = "";
} else {
throw new SqlToolError(rb.getString(
SqltoolRB.SYSPROP_UNDEFINED, varName));
}
}
expandBuffer.replace(b, e + 1, varValue);
}
iterations = 0;
while (true) {
s = expandBuffer.toString();
b = s.indexOf("*{");
if (b < 0) {
// No more unexpanded variable uses
break;
}
e = s.indexOf('}', b + 2);
if (e == b + 2) {
throw new SqlToolError(rb.getString(SqltoolRB.PLVAR_NAMEEMPTY));
}
if (e < 0) {
throw new SqlToolError(rb.getString(
SqltoolRB.PLVAR_UNTERMINATED));
}
permitUnset = (s.charAt(b + 2) == ':');
varName = s.substring(b + (permitUnset ? 3 : 2), e);
if (iterations++ > 10000)
throw new SqlToolError(rb.getString(SqltoolRB.VAR_INFINITE,
varName));
// TODO: Use a smarter algorithm to handle (or prohibit)
// recursion without this clumsy detection tactic.
varValue = (String) userVars.get(varName);
if (varValue == null) {
if (permitUnset) {
varValue = "";
} else {
throw new SqlToolError(rb.getString(
SqltoolRB.PLVAR_UNDEFINED, varName));
}
}
expandBuffer.replace(b, e + 1, varValue);
}
return expandBuffer.toString();
}
public boolean plMode = false;
// PL variable name currently awaiting query output.
private String fetchingVar = null;
private boolean silentFetch = false;
private boolean fetchBinary = false;
/**
* Process a Process Language Command.
* Nesting not supported yet.
*
* @param inString Complete command, including the leading '\' character.
* @throws BadSpecial special-command-specific errors.
* @throws SqlToolError all other errors, plus BreakException and
* ContinueException.
*/
private void processPL(String inString) throws BadSpecial, SqlToolError {
Matcher m = plPattern.matcher(dereference(inString, false));
if (!m.matches()) {
throw new BadSpecial(rb.getString(SqltoolRB.PL_MALFORMAT));
// I think it's impossible to get here, since the pattern is
// so liberal.
}
if (m.groupCount() < 1 || m.group(1) == null) {
plMode = true;
stdprintln(rb.getString(SqltoolRB.PL_EXPANSIONMODE, "on"));
return;
}
String[] tokens = m.group(1).split("\\s+");
if (tokens[0].charAt(0) == '?') {
stdprintln(rb.getString(SqltoolRB.PL_HELP));
return;
}
// If user runs any PL command, we turn PL mode on.
plMode = true;
if (tokens[0].equals("end")) {
throw new BadSpecial(rb.getString(SqltoolRB.END_NOBLOCK));
}
if (tokens[0].equals("continue")) {
if (tokens.length > 1) {
if (tokens.length == 2 &&
(tokens[1].equals("foreach") ||
tokens[1].equals("while"))) {
throw new ContinueException(tokens[1]);
}
throw new BadSpecial(rb.getString(SqltoolRB.CONTINUE_SYNTAX));
}
throw new ContinueException();
}
if (tokens[0].equals("break")) {
if (tokens.length > 1) {
if (tokens.length == 2 &&
(tokens[1].equals("foreach") ||
tokens[1].equals("if") ||
tokens[1].equals("while") ||
tokens[1].equals("file"))) {
throw new BreakException(tokens[1]);
}
throw new BadSpecial(rb.getString(SqltoolRB.BREAK_SYNTAX));
}
throw new BreakException();
}
if (tokens[0].equals("list") || tokens[0].equals("listvalues")
|| tokens[0].equals("listsysprops")) {
boolean sysProps =tokens[0].equals("listsysprops");
String s;
boolean doValues = (tokens[0].equals("listvalues") || sysProps);
// Always list System Property values.
// They are unlikely to be very long, like PL variables may be.
if (tokens.length == 1) {
stdprint(formatNicely(
(sysProps ? System.getProperties() : userVars),
doValues));
} else {
if (doValues) {
stdprintln(rb.getString(SqltoolRB.PL_LIST_PARENS));
} else {
stdprintln(rb.getString(SqltoolRB.PL_LIST_LENGTHS));
}
for (int i = 1; i < tokens.length; i++) {
s = (String) (sysProps ? System.getProperties() : userVars).
get(tokens[i]);
if (s == null) continue;
stdprintln(" " + tokens[i] + ": "
+ (doValues ? ("(" + s + ')')
: Integer.toString(s.length())));
}
}
return;
}
if (tokens[0].equals("dump") || tokens[0].equals("load")) {
if (tokens.length != 3) {
throw new BadSpecial(rb.getString(
SqltoolRB.DUMPLOAD_MALFORMAT));
}
String varName = tokens[1];
if (varName.indexOf(':') > -1) {
throw new BadSpecial(rb.getString(SqltoolRB.PLVAR_NOCOLON));
}
File file = new File(tokens[2]);
try {
if (tokens[0].equals("dump")) {
dump(varName, file);
} else {
load(varName, file, charset);
}
} catch (IOException ioe) {
throw new BadSpecial(rb.getString(SqltoolRB.DUMPLOAD_FAIL,
varName, file.toString()), ioe);
}
return;
}
if (tokens[0].equals("prepare")) {
if (tokens.length != 2) {
throw new BadSpecial(rb.getString(SqltoolRB.PREPARE_MALFORMAT));
}
if (userVars.get(tokens[1]) == null) {
throw new BadSpecial(rb.getString(
SqltoolRB.PLVAR_UNDEFINED, tokens[1]));
}
prepareVar = tokens[1];
doPrepare = true;
return;
}
if (tokens[0].equals("foreach")) {
Matcher foreachM= foreachPattern.matcher(
dereference(inString, false));
if (!foreachM.matches()) {
throw new BadSpecial(rb.getString(SqltoolRB.FOREACH_MALFORMAT));
}
if (foreachM.groupCount() != 2) {
throw new RuntimeException(
"foreach pattern matched, but captured "
+ foreachM.groupCount() + " groups");
}
String varName = foreachM.group(1);
if (varName.indexOf(':') > -1) {
throw new BadSpecial(rb.getString(SqltoolRB.PLVAR_NOCOLON));
}
String[] values = foreachM.group(2).split("\\s+");
File tmpFile = null;
String varVal;
try {
tmpFile = plBlockFile("foreach");
} catch (IOException ioe) {
throw new BadSpecial(rb.getString(SqltoolRB.PL_TEMPFILE_FAIL),
ioe);
}
String origval = (String) userVars.get(varName);
try {
SqlFile sf;
for (int i = 0; i < values.length; i++) {
try {
varVal = values[i];
userVars.put(varName, varVal);
updateUserSettings();
sf = new SqlFile(tmpFile, false, userVars);
sf.plMode = true;
sf.recursed = true;
// Share the possiblyUncommitted state
sf.possiblyUncommitteds = possiblyUncommitteds;
sf.execute(curConn, continueOnError);
} catch (ContinueException ce) {
String ceMessage = ce.getMessage();
if (ceMessage != null
&&!ceMessage.equals("foreach")) {
throw ce;
}
}
}
} catch (BreakException be) {
String beMessage = be.getMessage();
// Handle "foreach" and plain breaks (by doing nothing)
if (beMessage != null &&!beMessage.equals("foreach")) {
throw be;
}
} catch (QuitNow qn) {
throw qn;
} catch (Exception e) {
throw new BadSpecial(rb.getString(SqltoolRB.PL_BLOCK_FAIL), e);
}
if (origval == null) {
userVars.remove(varName);
updateUserSettings();
} else {
userVars.put(varName, origval);
}
if (tmpFile != null &&!tmpFile.delete()) {
throw new BadSpecial(rb.getString(
SqltoolRB.TEMPFILE_REMOVAL_FAIL, tmpFile.toString()));
}
return;
}
if (tokens[0].equals("if") || tokens[0].equals("while")) {
Matcher ifwhileM= ifwhilePattern.matcher(
dereference(inString, false));
if (!ifwhileM.matches()) {
throw new BadSpecial(rb.getString(SqltoolRB.IFWHILE_MALFORMAT));
}
if (ifwhileM.groupCount() != 1) {
throw new RuntimeException(
"if/while pattern matched, but captured "
+ ifwhileM.groupCount() + " groups");
}
String[] values =
ifwhileM.group(1).replaceAll("!([a-zA-Z0-9*])", "! $1").
replaceAll("([a-zA-Z0-9*])!", "$1 !").split("\\s+");
File tmpFile = null;
if (tokens[0].equals("if")) {
try {
tmpFile = plBlockFile("if");
} catch (IOException ioe) {
throw new BadSpecial(rb.getString(SqltoolRB.PL_BLOCK_FAIL),
ioe);
}
try {
if (eval(values)) {
SqlFile sf = new SqlFile(tmpFile, false, userVars);
sf.plMode = true;
sf.recursed = true;
// Share the possiblyUncommitted state
sf.possiblyUncommitteds = possiblyUncommitteds;
sf.execute(curConn, continueOnError);
}
} catch (BreakException be) {
String beMessage = be.getMessage();
// Handle "if" and plain breaks (by doing nothing)
if (beMessage == null ||!beMessage.equals("if")) {
throw be;
}
} catch (ContinueException ce) {
throw ce;
} catch (QuitNow qn) {
throw qn;
} catch (BadSpecial bs) {
bs.appendMessage(rb.getString(SqltoolRB.IF_MALFORMAT));
throw bs;
} catch (Exception e) {
throw new BadSpecial(
rb.getString(SqltoolRB.PL_BLOCK_FAIL), e);
}
} else if (tokens[0].equals("while")) {
try {
tmpFile = plBlockFile("while");
} catch (IOException ioe) {
throw new BadSpecial(
rb.getString(SqltoolRB.PL_TEMPFILE_FAIL), ioe);
}
try {
SqlFile sf;
while (eval(values)) {
try {
sf = new SqlFile(tmpFile, false, userVars);
sf.recursed = true;
// Share the possiblyUncommitted state
sf.possiblyUncommitteds = possiblyUncommitteds;
sf.plMode = true;
sf.execute(curConn, continueOnError);
} catch (ContinueException ce) {
String ceMessage = ce.getMessage();
if (ceMessage != null &&!ceMessage.equals("while")) {
throw ce;
}
}
}
} catch (BreakException be) {
String beMessage = be.getMessage();
// Handle "while" and plain breaks (by doing nothing)
if (beMessage != null &&!beMessage.equals("while")) {
throw be;
}
} catch (QuitNow qn) {
throw qn;
} catch (BadSpecial bs) {
bs.appendMessage(rb.getString(SqltoolRB.WHILE_MALFORMAT));
throw bs;
} catch (Exception e) {
throw new BadSpecial(rb.getString(SqltoolRB.PL_BLOCK_FAIL),
e);
}
} else {
// Assertion
throw new RuntimeException(rb.getString(SqltoolRB.PL_UNKNOWN,
tokens[0]));
}
if (tmpFile != null &&!tmpFile.delete()) {
throw new BadSpecial(rb.getString(
SqltoolRB.TEMPFILE_REMOVAL_FAIL, tmpFile.toString()));
}
return;
}
m = varsetPattern.matcher(dereference(inString, false));
if (!m.matches()) {
throw new BadSpecial(rb.getString(SqltoolRB.PL_UNKNOWN, tokens[0]));
}
if (m.groupCount() < 2 || m.groupCount() > 3) {
// Assertion
throw new RuntimeException("varset pattern matched but captured "
+ m.groupCount() + " groups");
}
String varName = m.group(1);
if (varName.indexOf(':') > -1) {
throw new BadSpecial(rb.getString(SqltoolRB.PLVAR_NOCOLON));
}
switch (m.group(2).charAt(0)) {
case '_' :
silentFetch = true;
case '~' :
if (m.groupCount() > 2 && m.group(3) != null) {
throw new BadSpecial(rb.getString(
SqltoolRB.PLVAR_TILDEDASH_NOMOREARGS, m.group(3)));
}
userVars.remove(varName);
updateUserSettings();
fetchingVar = varName;
return;
case '=' :
if (fetchingVar != null && fetchingVar.equals(varName)) {
fetchingVar = null;
}
if (m.groupCount() > 2 && m.group(3) != null) {
userVars.put(varName, m.group(3));
} else {
userVars.remove(varName);
}
updateUserSettings();
return;
}
throw new BadSpecial(rb.getString(SqltoolRB.PL_UNKNOWN, tokens[0]));
// I think this would already be caught in the setvar block above.
}
/*
* Read a PL block into a new temp file.
*
* WARNING!!! foreach blocks are not yet smart about comments
* and strings. We just look for a line beginning with a PL "end"
* command without worrying about comments or quotes (for now).
*
* WARNING!!! This is very rudimentary.
* Users give up all editing and feedback capabilities while
* in the foreach loop.
* A better solution would be to pass current input stream to a
* new SqlFile.execute() with a mode whereby commands are written
* to a separate history but not executed.
*
* @throws IOException
* @throws SqlToolError
*/
private File plBlockFile(String seeking) throws IOException, SqlToolError {
String s;
// Have already read the if/while/foreach statement, so we are already
// at nest level 1. When we reach nestlevel 1 (read 1 net "end"
// statement), we're at level 0 and return.
int nestlevel = 1;
String curPlCommand;
if (seeking == null
|| ((!seeking.equals("foreach")) && (!seeking.equals("if"))
&& (!seeking.equals("while")))) {
throw new RuntimeException(
"Assertion failed. Unsupported PL block type: " + seeking);
}
File tmpFile = File.createTempFile("sqltool-", ".sql");
PrintWriter pw = new PrintWriter((charset == null)
? (new OutputStreamWriter(new FileOutputStream(tmpFile)))
: (new OutputStreamWriter(new FileOutputStream(tmpFile),
charset)));
// Replace with just "(new FileOutputStream(file), charset)"
// once use defaultCharset from Java 1.5 in charset init. above.
try {
pw.println("/* " + (new java.util.Date()) + ". "
+ getClass().getName() + " PL block. */");
pw.println();
Matcher m;
while (true) {
s = br.readLine();
if (s == null) {
s = rb.getString(SqltoolRB.PL_BLOCK_UNTERMINATED, seeking);
errprintln(s);
throw new SqlToolError(s);
}
curLinenum++;
m = plPattern.matcher(s);
if (m.matches() && m.groupCount() > 0 && m.group(1) != null) {
String[] tokens = m.group(1).split("\\s+");
curPlCommand = tokens[0];
// PL COMMAND of some sort.
if (curPlCommand.equals(seeking)) {
nestlevel++;
} else if (curPlCommand.equals("end")) {
if (tokens.length < 2) {
s = rb.getString(SqltoolRB.END_SYNTAX, "1");
errprintln(s);
throw new SqlToolError(s);
}
String inType = tokens[1];
if (inType.equals(seeking)) {
nestlevel--;
if (nestlevel < 1) {
break;
}
}
if ((!inType.equals("foreach")) && (!inType.equals("if"))
&& (!inType.equals("while"))) {
s = rb.getString(SqltoolRB.END_SYNTAX, "2");
errprintln(s);
throw new SqlToolError(s);
}
}
}
pw.println(s);
}
pw.flush();
} finally {
pw.close();
}
return tmpFile;
}
/**
* Wrapper methods so don't need to call x(..., false) in most cases.
*/
/* Unused. Enable when/if need.
private void stdprintln() {
stdprintln(false);
}
*/
private void stdprint(String s) {
stdprint(s, false);
}
private void stdprintln(String s) {
stdprintln(s, false);
}
/**
* Encapsulates normal output.
*
* Conditionally HTML-ifies output.
*/
private void stdprintln(boolean queryOutput) {
if (htmlMode) {
psStd.println("<BR>");
} else {
psStd.println();
}
if (queryOutput && pwQuery != null) {
if (htmlMode) {
pwQuery.println("<BR>");
} else {
pwQuery.println();
}
pwQuery.flush();
}
}
/**
* Encapsulates error output.
*
* Conditionally HTML-ifies error output.
*/
private void errprint(String s) {
psErr.print(htmlMode
? ("<DIV style='color:white; background: red; "
+ "font-weight: bold'>" + s + "</DIV>")
: s);
}
/**
* Encapsulates error output.
*
* Conditionally HTML-ifies error output.
*/
private void errprintln(String s) {
psErr.println(htmlMode
? ("<DIV style='color:white; background: red; "
+ "font-weight: bold'>" + s + "</DIV>")
: s);
}
/**
* Encapsulates normal output.
*
* Conditionally HTML-ifies output.
*/
private void stdprint(String s, boolean queryOutput) {
psStd.print(htmlMode ? ("<P>" + s + "</P>")
: s);
if (queryOutput && pwQuery != null) {
pwQuery.print(htmlMode ? ("<P>" + s + "</P>")
: s);
pwQuery.flush();
}
}
/**
* Encapsulates normal output.
*
* Conditionally HTML-ifies output.
*/
private void stdprintln(String s, boolean queryOutput) {
psStd.println(htmlMode ? ("<P>" + s + "</P>")
: s);
if (queryOutput && pwQuery != null) {
pwQuery.println(htmlMode ? ("<P>" + s + "</P>")
: s);
pwQuery.flush();
}
}
// Just because users may be used to seeing "[null]" in normal
// SqlFile output, we use the same default value for null in DSV
// files, but this DSV null representation can be changed to anything.
private static final String DEFAULT_NULL_REP = "[null]";
private static final String DEFAULT_ROW_DELIM = LS;
private static final String DEFAULT_COL_DELIM = "|";
private static final String DEFAULT_SKIP_PREFIX = "#";
private static final int DEFAULT_ELEMENT = 0,
HSQLDB_ELEMENT = 1,
ORACLE_ELEMENT = 2
;
// These do not specify order listed, just inclusion.
private static final int[] listMDSchemaCols = { 1 };
private static final int[] listMDIndexCols = {
2, 6, 3, 9, 4, 10, 11
};
/** Column numbering starting at 1. */
private static final int[][] listMDTableCols = {
{
2, 3
}, // Default
{
2, 3
}, // HSQLDB
{
2, 3
}, // Oracle
};
/**
* SYS and SYSTEM are the only base system accounts in Oracle, however,
* from an empirical perspective, all of these other accounts are
* system accounts because <UL>
* <LI> they are hidden from the casual user
* <LI> they are created by the installer at installation-time
* <LI> they are used automatically by the Oracle engine when the
* specific Oracle sub-product is used
* <LI> the accounts should not be <I>messed with</I> by database users
* <LI> the accounts should certainly not be used if the specific
* Oracle sub-product is going to be used.
* </UL>
*
* General advice: If you aren't going to use an Oracle sub-product,
* then <B>don't install it!</B>
* Don't blindly accept default when running OUI.
*
* If users also see accounts that they didn't create with names like
* SCOTT, ADAMS, JONES, CLARK, BLAKE, OE, PM, SH, QS, QS_*, these
* contain sample data and the schemas can safely be removed.
*/
private static final String[] oracleSysSchemas = {
"SYS", "SYSTEM", "OUTLN", "DBSNMP", "OUTLN", "MDSYS", "ORDSYS",
"ORDPLUGINS", "CTXSYS", "DSSYS", "PERFSTAT", "WKPROXY", "WKSYS",
"WMSYS", "XDB", "ANONYMOUS", "ODM", "ODM_MTR", "OLAPSYS", "TRACESVR",
"REPADMIN"
};
/**
* Lists available database tables.
*
* When a filter is given, we assume that there are no lower-case
* characters in the object names (which would require "quotes" when
* creating them).
*
* @throws BadSpecial usually wrap a cause (which cause is a
* SQLException in some cases).
* @throws SqlToolError passed through from other methods in this class.
*/
private void listTables(char c, String inFilter) throws BadSpecial,
SqlToolError {
String schema = null;
int[] listSet = null;
String[] types = null;
/** For workaround for \T for Oracle */
String[] additionalSchemas = null;
/** This is for specific non-getTable() queries */
Statement statement = null;
ResultSet rs = null;
String narrower = "";
/*
* Doing case-sensitive filters now, for greater portability.
String filter = ((inFilter == null)
? null : inFilter.toUpperCase());
*/
String filter = inFilter;
try {
DatabaseMetaData md = curConn.getMetaData();
String dbProductName = md.getDatabaseProductName();
//System.err.println("DB NAME = (" + dbProductName + ')');
// Database-specific table filtering.
/* 3 Types of actions:
* 1) Special handling. Return from the "case" block directly.
* 2) Execute a specific query. Set statement in the "case".
* 3) Otherwise, set filter info for dbmd.getTable() in the
* "case".
*/
types = new String[1];
switch (c) {
case '*' :
types = null;
break;
case 'S' :
if (dbProductName.indexOf("Oracle") > -1) {
errprintln(rb.getString(SqltoolRB.VENDOR_ORACLE_DS));
types[0] = "TABLE";
schema = "SYS";
additionalSchemas = oracleSysSchemas;
} else {
types[0] = "SYSTEM TABLE";
}
break;
case 's' :
if (dbProductName.indexOf("HSQL") > -1) {
// HSQLDB does not consider Sequences as "tables",
// hence we do not list them in
// DatabaseMetaData.getTables().
if (filter != null
&& filter.charAt(filter.length() - 1)
== '.') {
narrower =
"\nWHERE sequence_schema = '"
+ filter.substring(0, filter.length() - 1)
+ "'";
filter = null;
}
statement = curConn.createStatement();
statement.execute(
"SELECT sequence_schema, sequence_name FROM "
+ "information_schema.system_sequences"
+ narrower);
} else {
types[0] = "SEQUENCE";
}
break;
case 'r' :
if (dbProductName.indexOf("HSQL") > -1) {
statement = curConn.createStatement();
statement.execute(
"SELECT authorization_name FROM "
+ "information_schema.system_authorizations\n"
+ "WHERE authorization_type = 'ROLE'\n"
+ "ORDER BY authorization_name");
} else if (dbProductName.indexOf(
"Adaptive Server Enterprise") > -1) {
// This is the basic Sybase server. Sybase also has
// their "Anywhere", ASA (for embedded), and replication
// databases, but I don't know the Metadata strings for
// those.
statement = curConn.createStatement();
statement.execute(
"SELECT name FROM syssrvroles ORDER BY name");
} else if (dbProductName.indexOf(
"Apache Derby") > -1) {
throw new BadSpecial(
rb.getString(SqltoolRB.VENDOR_DERBY_DR));
} else {
throw new BadSpecial(
rb.getString(SqltoolRB.VENDOR_NOSUP_D, "r"));
}
break;
case 'u' :
if (dbProductName.indexOf("HSQL") > -1) {
statement = curConn.createStatement();
statement.execute(
"SELECT user, admin FROM "
+ "information_schema.system_users\n"
+ "ORDER BY user");
} else if (dbProductName.indexOf("Oracle") > -1) {
statement = curConn.createStatement();
statement.execute(
"SELECT username, created FROM all_users "
+ "ORDER BY username");
} else if (dbProductName.indexOf("PostgreSQL") > -1) {
statement = curConn.createStatement();
statement.execute(
"SELECT usename, usesuper FROM pg_catalog.pg_user "
+ "ORDER BY usename");
} else if (dbProductName.indexOf(
"Adaptive Server Enterprise") > -1) {
// This is the basic Sybase server. Sybase also has
// their "Anywhere", ASA (for embedded), and replication
// databases, but I don't know the Metadata strings for
// those.
statement = curConn.createStatement();
statement.execute(
"SELECT name, accdate, fullname FROM syslogins "
+ "ORDER BY name");
} else if (dbProductName.indexOf(
"Apache Derby") > -1) {
throw new BadSpecial(
rb.getString(SqltoolRB.VENDOR_DERBY_DU));
} else {
throw new BadSpecial(
rb.getString(SqltoolRB.VENDOR_NOSUP_D, "u"));
}
break;
case 'a' :
if (dbProductName.indexOf("HSQL") > -1) {
// HSQLDB Aliases are not the same things as the
// aliases listed in DatabaseMetaData.getTables().
if (filter != null
&& filter.charAt(filter.length() - 1)
== '.') {
narrower =
"\nWHERE alias_schem = '"
+ filter.substring(0, filter.length() - 1)
+ "'";
filter = null;
}
statement = curConn.createStatement();
statement.execute(
"SELECT alias_schem, alias FROM "
+ "information_schema.system_aliases" + narrower);
} else {
types[0] = "ALIAS";
}
break;
case 't' :
excludeSysSchemas = (dbProductName.indexOf("Oracle")
> -1);
types[0] = "TABLE";
break;
case 'v' :
types[0] = "VIEW";
break;
case 'n' :
rs = md.getSchemas();
if (rs == null) {
throw new BadSpecial(
"Failed to get metadata from database");
}
displayResultSet(null, rs, listMDSchemaCols, filter);
return;
case 'i' :
// Some databases require to specify table, some don't.
/*
if (filter == null) {
throw new BadSpecial("You must specify the index's "
+ "table as argument to \\di");
}
*/
String table = null;
if (filter != null) {
int dotat = filter.indexOf('.');
schema = ((dotat > 0) ? filter.substring(0, dotat)
: null);
if (dotat < filter.length() - 1) {
// Not a schema-only specifier
table = ((dotat > 0) ? filter.substring(dotat + 1)
: filter);
}
filter = null;
}
// N.b. Oracle incorrectly reports the INDEX SCHEMA as
// the TABLE SCHEMA. The Metadata structure seems to
// be designed with the assumption that the INDEX schema
// will be the same as the TABLE schema.
rs = md.getIndexInfo(null, schema, table, false, true);
if (rs == null) {
throw new BadSpecial(
"Failed to get metadata from database");
}
displayResultSet(null, rs, listMDIndexCols, null);
return;
default :
throw new BadSpecial(rb.getString(
SqltoolRB.SPECIAL_D_UNKNOWN,
Character.toString(c)) + LS + D_OPTIONS_TEXT);
}
if (statement == null) {
if (dbProductName.indexOf("HSQL") > -1) {
listSet = listMDTableCols[HSQLDB_ELEMENT];
} else if (dbProductName.indexOf("Oracle") > -1) {
listSet = listMDTableCols[ORACLE_ELEMENT];
} else {
listSet = listMDTableCols[DEFAULT_ELEMENT];
}
if (schema == null && filter != null
&& filter.charAt(filter.length() - 1) == '.') {
schema = filter.substring(0, filter.length() - 1);
filter = null;
}
}
rs = ((statement == null)
? md.getTables(null, schema, null, types)
: statement.getResultSet());
if (rs == null) {
throw new BadSpecial(rb.getString(
SqltoolRB.METADATA_FETCH_FAIL));
}
displayResultSet(null, rs, listSet, filter);
if (additionalSchemas != null) {
for (int i = 1; i < additionalSchemas.length; i++) {
/*
* Inefficient, but we have to do each successful query
* twice in order to prevent calling displayResultSet
* for empty/non-existent schemas
*/
rs = md.getTables(null, additionalSchemas[i], null,
types);
if (rs == null) {
throw new BadSpecial(rb.getString(
SqltoolRB.METADATA_FETCH_FAILFOR,
additionalSchemas[i]));
}
if (!rs.next()) {
continue;
}
displayResultSet(
null,
md.getTables(
null, additionalSchemas[i], null, types), listSet, filter);
}
}
} catch (SQLException se) {
throw new BadSpecial(rb.getString( SqltoolRB.METADATA_FETCH_FAIL),
se);
} catch (NullPointerException npe) {
throw new BadSpecial(rb.getString( SqltoolRB.METADATA_FETCH_FAIL),
npe);
} finally {
excludeSysSchemas = false;
if (rs != null) {
rs = null;
}
if (statement != null) {
try {
statement.close();
} catch (Exception e) {}
statement = null;
}
}
}
private boolean excludeSysSchemas = false;
/**
* Process the contents of Edit Buffer as an SQL Statement
*
* @throws SQLException thrown by JDBC driver.
* @throws SqlToolError all other errors.
*/
private void processSQL() throws SQLException, SqlToolError {
// Really don't know whether to take the network latency hit here
// in order to check autoCommit in order to set
// possiblyUncommitteds more accurately.
// I'm going with "NO" for now, since autoCommit will usually be off.
// If we do ever check autocommit, we have to keep track of the
// autocommit state when every SQL statement is run, since I may
// be able to have uncommitted DML, turn autocommit on, then run
// other DDL with autocommit on. As a result, I could be running
// SQL commands with autotommit on but still have uncommitted mods.
// (For all I know, the database could commit or rollback whenever
// the autocommit option is changed, and that behavior could be
// DB-specific).
lastSqlStatement = (plMode ? dereference(buffer, true)
: buffer);
Statement statement = null;
if (doPrepare) {
if (lastSqlStatement.indexOf('?') < 1) {
lastSqlStatement = null;
throw new SqlToolError(rb.getString(
SqltoolRB.PREPARE_DEMANDQM));
}
doPrepare = false;
PreparedStatement ps = curConn.prepareStatement(lastSqlStatement);
if (prepareVar == null) {
if (binBuffer == null) {
lastSqlStatement = null;
throw new SqlToolError(rb.getString(
SqltoolRB.BINBUFFER_EMPTY));
}
ps.setBytes(1, binBuffer);
} else {
String val = (String) userVars.get(prepareVar);
if (val == null) {
lastSqlStatement = null;
throw new SqlToolError(
rb.getString(SqltoolRB.PLVAR_UNDEFINED,
prepareVar));
}
prepareVar = null;
ps.setString(1, val);
}
ps.executeUpdate();
statement = ps;
} else {
statement = curConn.createStatement();
statement.execute(lastSqlStatement);
}
possiblyUncommitteds.set(true);
try {
displayResultSet(statement, statement.getResultSet(), null, null);
} finally {
try {
statement.close();
} catch (Exception e) {}
}
lastSqlStatement = null;
}
/**
* Display the given result set for user.
* The last 3 params are to narrow down records and columns where
* that can not be done with a where clause (like in metadata queries).
*
* @param statement The SQL Statement that the result set is for.
* (This is so we can get the statement's update count.
* Can be null for non-update queries.)
* @param r The ResultSet to display.
* @param incCols Optional list of which columns to include (i.e., if
* given, then other columns will be skipped).
* @param incFilter Optional case-insensitive substring.
* Rows are skipped which to not contain this substring.
* @throws SQLException thrown by JDBC driver.
* @throws SqlToolError all other errors.
*/
private void displayResultSet(Statement statement, ResultSet r,
int[] incCols,
String filter) throws SQLException,
SqlToolError {
java.sql.Timestamp ts;
int dotAt;
int updateCount = (statement == null) ? -1
: statement
.getUpdateCount();
boolean silent = silentFetch;
boolean binary = fetchBinary;
silentFetch = false;
fetchBinary = false;
if (excludeSysSchemas) {
stdprintln(rb.getString(SqltoolRB.VENDOR_NOSUP_SYSSCHEMAS));
}
switch (updateCount) {
case -1 :
if (r == null) {
stdprintln(rb.getString(SqltoolRB.NORESULT), true);
break;
}
ResultSetMetaData m = r.getMetaData();
int cols = m.getColumnCount();
int incCount = (incCols == null) ? cols
: incCols
.length;
String val;
List rows = new ArrayList();
String[] headerArray = null;
String[] fieldArray;
int[] maxWidth = new int[incCount];
int insi;
boolean skip;
// STEP 1: GATHER DATA
if (!htmlMode) {
for (int i = 0; i < maxWidth.length; i++) {
maxWidth[i] = 0;
}
}
boolean[] rightJust = new boolean[incCount];
int[] dataType = new int[incCount];
boolean[] autonulls = new boolean[incCount];
insi = -1;
headerArray = new String[incCount];
for (int i = 1; i <= cols; i++) {
if (incCols != null) {
skip = true;
for (int j = 0; j < incCols.length; j++) {
if (i == incCols[j]) {
skip = false;
}
}
if (skip) {
continue;
}
}
headerArray[++insi] = m.getColumnLabel(i);
dataType[insi] = m.getColumnType(i);
rightJust[insi] = false;
autonulls[insi] = true;
switch (dataType[insi]) {
case java.sql.Types.BIGINT :
case java.sql.Types.BIT :
case java.sql.Types.DECIMAL :
case java.sql.Types.DOUBLE :
case java.sql.Types.FLOAT :
case java.sql.Types.INTEGER :
case java.sql.Types.NUMERIC :
case java.sql.Types.REAL :
case java.sql.Types.SMALLINT :
case java.sql.Types.TINYINT :
rightJust[insi] = true;
break;
case java.sql.Types.VARBINARY :
case java.sql.Types.VARCHAR :
case java.sql.Types.ARRAY :
// Guessing at how to handle ARRAY.
case java.sql.Types.BLOB :
case java.sql.Types.CLOB :
case java.sql.Types.LONGVARBINARY :
case java.sql.Types.LONGVARCHAR :
autonulls[insi] = false;
break;
}
if (htmlMode) {
continue;
}
if (headerArray[insi].length() > maxWidth[insi]) {
maxWidth[insi] = headerArray[insi].length();
}
}
boolean filteredOut;
while (r.next()) {
fieldArray = new String[incCount];
insi = -1;
filteredOut = filter != null;
for (int i = 1; i <= cols; i++) {
// This is the only case where we can save a data
// read by recognizing we don't need this datum early.
if (incCols != null) {
skip = true;
for (int j = 0; j < incCols.length; j++) {
if (i == incCols[j]) {
skip = false;
}
}
if (skip) {
continue;
}
}
// This row may still be ditched, but it is now
// certain that we need to increment the fieldArray
// index.
++insi;
if (!SqlFile.canDisplayType(dataType[insi])) {
binary = true;
}
val = null;
if (!binary) {
/*
* The special formatting for all time-related
* fields is because the most popular current
* databases are extremely inconsistent about
* what resolution is returned for the same types.
* In my experience so far, Dates MAY have
* resolution down to second, but only TIMESTAMPs
* support sub-second res. (and always can).
* On top of that there is no consistency across
* getObject().toString(). Oracle doesn't even
* implement it for their custom TIMESTAMP type.
*/
switch (dataType[insi]) {
case java.sql.Types.TIMESTAMP:
case java.sql.Types.DATE:
case java.sql.Types.TIME:
ts = r.getTimestamp(i);
val = ((ts == null) ? null : ts.toString());
// Following block truncates non-zero
// sub-seconds from time types OTHER than
// TIMESTAMP.
if (dataType[insi]
!= java.sql.Types.TIMESTAMP
&& val != null) {
dotAt = val.lastIndexOf('.');
for (int z = dotAt + 1;
z < val.length(); z++) {
if (val.charAt(z) != '0') {
dotAt = 0;
break;
}
}
if (dotAt > 1) {
val = val.substring(0, dotAt);
}
}
break;
default:
val = r.getString(i);
// If we tried to get a String but it
// failed, try getting it with a String
// Stream
if (val == null) {
try {
val = streamToString(
r.getAsciiStream(i), charset);
} catch (Exception e) { }
}
}
}
if (binary || (val == null &&!r.wasNull())) {
if (pwDsv != null) {
throw new SqlToolError(
rb.getString(SqltoolRB.DSV_BINCOL));
}
// DB has a value but we either explicitly want
// it as binary, or we failed to get it as String.
try {
binBuffer =
SqlFile.streamToBytes(r.getBinaryStream(i));
} catch (IOException ioe) {
throw new SqlToolError(
"Failed to read value using stream",
ioe);
}
stdprintln(rb.getString(SqltoolRB.BINBUF_WRITE,
Integer.toString(binBuffer.length),
headerArray[insi],
SqlFile.sqlTypeToString(dataType[insi])
));
return;
}
if (excludeSysSchemas && val != null && i == 2) {
for (int z = 0; z < oracleSysSchemas.length;
z++) {
if (val.equals(oracleSysSchemas[z])) {
filteredOut = true;
break;
}
}
}
userVars.put("?", ((val == null) ? nullRepToken : val));
if (fetchingVar != null) {
userVars.put(fetchingVar, userVars.get("?"));
updateUserSettings();
fetchingVar = null;
}
if (silent) {
return;
}
// We do not omit rows here. We collect information
// so we can make the decision after all rows are
// read in.
if (filter != null
&& (val == null
|| val.indexOf(filter) > -1)) {
filteredOut = false;
}
///////////////////////////////
// A little tricky here. fieldArray[] MUST get set.
if (val == null && pwDsv == null) {
if (dataType[insi] == java.sql.Types.VARCHAR) {
fieldArray[insi] = (htmlMode ? "<I>null</I>"
: nullRepToken);
} else {
fieldArray[insi] = "";
}
} else {
fieldArray[insi] = val;
}
///////////////////////////////
if (htmlMode || pwDsv != null) {
continue;
}
if (fieldArray[insi].length() > maxWidth[insi]) {
maxWidth[insi] = fieldArray[insi].length();
}
}
if (!filteredOut) {
rows.add(fieldArray);
}
}
// STEP 2: DISPLAY DATA (= 2a OR 2b)
// STEP 2a (Non-DSV)
if (pwDsv == null) {
condlPrintln("<TABLE border='1'>", true);
if (incCount > 1) {
condlPrint(SqlFile.htmlRow(COL_HEAD) + LS + PRE_TD, true);
for (int i = 0; i < headerArray.length; i++) {
condlPrint("<TD>" + headerArray[i] + "</TD>",
true);
condlPrint(((i > 0) ? SqlFile.spaces(2)
: "") + SqlFile.pad(
headerArray[i],
maxWidth[i],
rightJust[i],
(i < headerArray.length
- 1 || rightJust[i])), false);
}
condlPrintln(LS + PRE_TR + "</TR>", true);
condlPrintln("", false);
if (!htmlMode) {
for (int i = 0; i < headerArray.length; i++) {
condlPrint(((i > 0) ? SqlFile.spaces(2)
: "") + SqlFile.divider(
maxWidth[i]), false);
}
condlPrintln("", false);
}
}
for (int i = 0; i < rows.size(); i++) {
condlPrint(SqlFile.htmlRow(((i % 2) == 0) ? COL_EVEN
: COL_ODD) + LS
+ PRE_TD, true);
fieldArray = (String[]) rows.get(i);
for (int j = 0; j < fieldArray.length; j++) {
condlPrint("<TD>" + fieldArray[j] + "</TD>",
true);
condlPrint(((j > 0) ? SqlFile.spaces(2)
: "") + SqlFile.pad(
fieldArray[j],
maxWidth[j],
rightJust[j],
(j < fieldArray.length
- 1 || rightJust[j])), false);
}
condlPrintln(LS + PRE_TR + "</TR>", true);
condlPrintln("", false);
}
condlPrintln("</TABLE>", true);
if (rows.size() != 1) {
stdprintln(LS + rb.getString(SqltoolRB.ROWS_FETCHED,
rows.size()), true);
}
condlPrintln("<HR>", true);
break;
}
// STEP 2b (DSV)
if (incCount > 0) {
for (int i = 0; i < headerArray.length; i++) {
dsvSafe(headerArray[i]);
pwDsv.print(headerArray[i]);
if (i < headerArray.length - 1) {
pwDsv.print(dsvColDelim);
}
}
pwDsv.print(dsvRowDelim);
}
for (int i = 0; i < rows.size(); i++) {
fieldArray = (String[]) rows.get(i);
for (int j = 0; j < fieldArray.length; j++) {
dsvSafe(fieldArray[j]);
pwDsv.print((fieldArray[j] == null)
? (autonulls[j] ? ""
: nullRepToken)
: fieldArray[j]);
if (j < fieldArray.length - 1) {
pwDsv.print(dsvColDelim);
}
}
pwDsv.print(dsvRowDelim);
}
stdprintln(rb.getString(SqltoolRB.ROWS_FETCHED_DSV,
rows.size()));
break;
default :
userVars.put("?", Integer.toString(updateCount));
if (fetchingVar != null) {
userVars.put(fetchingVar, userVars.get("?"));
updateUserSettings();
fetchingVar = null;
}
if (updateCount != 0) {
stdprintln((updateCount == 1)
? rb.getString(SqltoolRB.ROW_UPDATE_SINGULAR)
: rb.getString(SqltoolRB.ROW_UPDATE_MULTIPLE,
updateCount));
}
break;
}
}
private static final int COL_HEAD = 0,
COL_ODD = 1,
COL_EVEN = 2
;
private static final String PRE_TR = spaces(4);
private static final String PRE_TD = spaces(8);
/**
* Print a properly formatted HTML <TR> command for the given
* situation.
*
* @param colType Column type: COL_HEAD, COL_ODD or COL_EVEN.
*/
private static String htmlRow(int colType) {
switch (colType) {
case COL_HEAD :
return PRE_TR + "<TR style='font-weight: bold;'>";
case COL_ODD :
return PRE_TR
+ "<TR style='background: #94d6ef; font: normal "
+ "normal 10px/10px Arial, Helvitica, sans-serif;'>";
case COL_EVEN :
return PRE_TR
+ "<TR style='background: silver; font: normal "
+ "normal 10px/10px Arial, Helvitica, sans-serif;'>";
}
return null;
}
/**
* Returns a divider of hypens of requested length.
*
* @param len Length of output String.
*/
private static String divider(int len) {
return (len > DIVIDER.length()) ? DIVIDER
: DIVIDER.substring(0, len);
}
/**
* Returns a String of spaces of requested length.
*
* @param len Length of output String.
*/
private static String spaces(int len) {
return (len > SPACES.length()) ? SPACES
: SPACES.substring(0, len);
}
/**
* Pads given input string out to requested length with space
* characters.
*
* @param inString Base string.
* @param fulllen Output String length.
* @param rightJustify True to right justify, false to left justify.
*/
private static String pad(String inString, int fulllen,
boolean rightJustify, boolean doPad) {
if (!doPad) {
return inString;
}
int len = fulllen - inString.length();
if (len < 1) {
return inString;
}
String pad = SqlFile.spaces(len);
return ((rightJustify ? pad
: "") + inString + (rightJustify ? ""
: pad));
}
/**
* Display command history.
*/
private void showHistory() throws BadSpecial {
if (history == null) {
throw new BadSpecial(rb.getString(SqltoolRB.HISTORY_UNAVAILABLE));
}
if (history.size() < 1) {
throw new BadSpecial(rb.getString(SqltoolRB.HISTORY_NONE));
}
for (int i = 0; i < history.size(); i++) {
psStd.println("#" + (i + oldestHist) + " or "
+ (i - history.size()) + ':');
psStd.println((String) history.get(i));
}
if (buffer != null) {
psStd.println(rb.getString(SqltoolRB.EDITBUFFER_CONTENTS, buffer));
}
psStd.println();
psStd.println(rb.getString(SqltoolRB.BUFFER_INSTRUCTIONS));
}
/**
* Return a Command from command history.
*/
private String commandFromHistory(int inIndex) throws BadSpecial {
int index = inIndex; // Just to quiet compiler warnings.
if (history == null) {
throw new BadSpecial(rb.getString(SqltoolRB.HISTORY_UNAVAILABLE));
}
if (index == 0) {
throw new BadSpecial(rb.getString(SqltoolRB.HISTORY_NUMBER_REQ));
}
if (index > 0) {
// Positive command# given
index -= oldestHist;
if (index < 0) {
throw new BadSpecial(rb.getString(SqltoolRB.HISTORY_BACKTO,
oldestHist));
}
if (index >= history.size()) {
throw new BadSpecial(rb.getString(SqltoolRB.HISTORY_UPTO,
history.size() + oldestHist - 1));
}
} else {
// Negative command# given
index += history.size();
if (index < 0) {
throw new BadSpecial(rb.getString(SqltoolRB.HISTORY_BACK,
history.size()));
}
}
return (String) history.get(index);
}
/**
* Search Command History for a regex match.
*
* @returns Absolute command number, if any match.
*/
private Integer historySearch(String findRegex) throws BadSpecial {
if (history == null) {
throw new BadSpecial(rb.getString(SqltoolRB.HISTORY_UNAVAILABLE));
}
Pattern pattern = Pattern.compile("(?ims)" + findRegex);
// Make matching more liberal. Users can customize search behavior
// by using "(?-OPTIONS)" or (?OPTIONS) in their regexes.
for (int index = history.size() - 1; index >= 0; index--)
if (pattern.matcher((String) history.get(index)).find())
return new Integer(index + oldestHist);
return null;
}
private void setBuf(String newContent) {
buffer = new String(newContent);
// System.err.println("Buffer is now (" + buffer + ')');
}
int oldestHist = 1;
/**
* Add a command onto the history list.
*/
private void historize() {
if (history == null || buffer == null) {
return;
}
if (history.size() > 0 &&
history.get(history.size() - 1).equals(buffer)) {
// Don't store two consecutive commands that are exactly the same.
return;
}
history.add(buffer);
if (history.size() <= maxHistoryLength) {
return;
}
history.remove(0);
oldestHist++;
}
/**
* Describe the columns of specified table.
*
* @param tableName Table that will be described.
* @param filter Substring to filter by
*/
private void describe(String tableName,
String inFilter) throws SQLException {
/*
* Doing case-sensitive filters now, for greater portability.
String filter = ((inFilter == null) ? null : inFilter.toUpperCase());
*/
String filter = inFilter;
List rows = new ArrayList();
String[] headerArray = {
rb.getString(SqltoolRB.DESCRIBE_TABLE_NAME),
rb.getString(SqltoolRB.DESCRIBE_TABLE_DATATYPE),
rb.getString(SqltoolRB.DESCRIBE_TABLE_WIDTH),
rb.getString(SqltoolRB.DESCRIBE_TABLE_NONULLS),
};
String[] fieldArray;
int[] maxWidth = {
0, 0, 0, 0
};
boolean[] rightJust = {
false, false, true, false
};
// STEP 1: GATHER DATA
for (int i = 0; i < headerArray.length; i++) {
if (htmlMode) {
continue;
}
if (headerArray[i].length() > maxWidth[i]) {
maxWidth[i] = headerArray[i].length();
}
}
Statement statement = curConn.createStatement();
ResultSet r = null;
try {
statement.execute("SELECT * FROM " + tableName + " WHERE 1 = 2");
r = statement.getResultSet();
ResultSetMetaData m = r.getMetaData();
int cols = m.getColumnCount();
for (int i = 0; i < cols; i++) {
fieldArray = new String[4];
fieldArray[0] = m.getColumnName(i + 1);
if (filter != null && fieldArray[0].indexOf(filter) < 0) {
continue;
}
fieldArray[1] = m.getColumnTypeName(i + 1);
fieldArray[2] = Integer.toString(m.getColumnDisplaySize(i + 1));
fieldArray[3] =
((m.isNullable(i + 1) == java.sql.ResultSetMetaData.columnNullable)
? (htmlMode ? " "
: "")
: "*");
rows.add(fieldArray);
for (int j = 0; j < fieldArray.length; j++) {
if (fieldArray[j].length() > maxWidth[j]) {
maxWidth[j] = fieldArray[j].length();
}
}
}
// STEP 2: DISPLAY DATA
condlPrint("<TABLE border='1'>" + LS + SqlFile.htmlRow(COL_HEAD) + LS
+ PRE_TD, true);
for (int i = 0; i < headerArray.length; i++) {
condlPrint("<TD>" + headerArray[i] + "</TD>", true);
condlPrint(((i > 0) ? SqlFile.spaces(2)
: "") + SqlFile.pad(headerArray[i], maxWidth[i],
rightJust[i],
(i < headerArray.length - 1
|| rightJust[i])), false);
}
condlPrintln(LS + PRE_TR + "</TR>", true);
condlPrintln("", false);
if (!htmlMode) {
for (int i = 0; i < headerArray.length; i++) {
condlPrint(((i > 0) ? SqlFile.spaces(2)
: "") + SqlFile.divider(maxWidth[i]), false);
}
condlPrintln("", false);
}
for (int i = 0; i < rows.size(); i++) {
condlPrint(SqlFile.htmlRow(((i % 2) == 0) ? COL_EVEN
: COL_ODD) + LS
+ PRE_TD, true);
fieldArray = (String[]) rows.get(i);
for (int j = 0; j < fieldArray.length; j++) {
condlPrint("<TD>" + fieldArray[j] + "</TD>", true);
condlPrint(((j > 0) ? SqlFile.spaces(2)
: "") + SqlFile.pad(
fieldArray[j], maxWidth[j],
rightJust[j],
(j < fieldArray.length - 1
|| rightJust[j])), false);
}
condlPrintln(LS + PRE_TR + "</TR>", true);
condlPrintln("", false);
}
condlPrintln(LS + "</TABLE>" + LS + "<HR>", true);
} finally {
try {
if (r != null) {
r.close();
r = null;
}
statement.close();
} catch (Exception e) {}
}
}
private boolean eval(String[] inTokens) throws BadSpecial {
/* TODO: Rewrite using java.util.regex. */
// dereference *VARNAME variables.
// N.b. we work with a "copy" of the tokens.
boolean negate = inTokens.length > 0 && inTokens[0].equals("!");
String[] tokens = new String[negate ? (inTokens.length - 1)
: inTokens.length];
String inToken;
for (int i = 0; i < tokens.length; i++) {
inToken = inTokens[i + (negate ? 1 : 0)];
if (inToken.length() > 1 && inToken.charAt(0) == '*') {
tokens[i] = (String) userVars.get(inToken.substring(1));
} else {
tokens[i] = inTokens[i + (negate ? 1 : 0)];
}
// Unset variables permitted in expressions as long as use
// the short *VARNAME form.
if (tokens[i] == null) {
tokens[i] = "";
}
}
if (tokens.length == 1) {
return (tokens[0].length() > 0 &&!tokens[0].equals("0")) ^ negate;
}
if (tokens.length == 3) {
if (tokens[1].equals("==")) {
return tokens[0].equals(tokens[2]) ^ negate;
}
if (tokens[1].equals("!=") || tokens[1].equals("<>")
|| tokens[1].equals("><")) {
return (!tokens[0].equals(tokens[2])) ^ negate;
}
if (tokens[1].equals(">")) {
return (tokens[0].length() > tokens[2].length() || ((tokens[0].length() == tokens[2].length()) && tokens[0].compareTo(tokens[2]) > 0))
^ negate;
}
if (tokens[1].equals("<")) {
return (tokens[2].length() > tokens[0].length() || ((tokens[2].length() == tokens[0].length()) && tokens[2].compareTo(tokens[0]) > 0))
^ negate;
}
}
throw new BadSpecial(rb.getString(SqltoolRB.LOGICAL_UNRECOGNIZED));
}
private void closeQueryOutputStream() {
if (pwQuery == null) {
return;
}
if (htmlMode) {
pwQuery.println("</BODY></HTML>");
pwQuery.flush();
}
pwQuery.close();
pwQuery = null;
}
/**
* Print to psStd and possibly pwQuery iff current HTML mode matches
* supplied printHtml.
*/
private void condlPrintln(String s, boolean printHtml) {
if ((printHtml &&!htmlMode) || (htmlMode &&!printHtml)) {
return;
}
psStd.println(s);
if (pwQuery != null) {
pwQuery.println(s);
pwQuery.flush();
}
}
/**
* Print to psStd and possibly pwQuery iff current HTML mode matches
* supplied printHtml.
*/
private void condlPrint(String s, boolean printHtml) {
if ((printHtml &&!htmlMode) || (htmlMode &&!printHtml)) {
return;
}
psStd.print(s);
if (pwQuery != null) {
pwQuery.print(s);
pwQuery.flush();
}
}
private String formatNicely(Map map, boolean withValues) {
String key;
StringBuffer sb = new StringBuffer();
Iterator it = (new TreeMap(map)).keySet().iterator();
if (withValues) {
SqlFile.appendLine(sb, rb.getString(SqltoolRB.PL_LIST_PARENS));
} else {
SqlFile.appendLine(sb, rb.getString(SqltoolRB.PL_LIST_LENGTHS));
}
while (it.hasNext()) {
key = (String) it.next();
String s = (String) map.get(key);
SqlFile.appendLine(sb, " " + key + ": " + (withValues ? ("(" + s + ')')
: Integer.toString(
s.length())));
}
return sb.toString();
}
/**
* Ascii file dump.
*
* dumpFile must not be null.
*/
private void dump(String varName,
File dumpFile) throws IOException, BadSpecial {
String val = (String) userVars.get(varName);
if (val == null) {
throw new BadSpecial(rb.getString(
SqltoolRB.PLVAR_UNDEFINED, varName));
}
OutputStreamWriter osw = ((charset == null)
? (new OutputStreamWriter(new FileOutputStream(dumpFile)))
: (new OutputStreamWriter(new FileOutputStream(dumpFile),
charset)));
// Replace with just "(new FileOutputStream(file), charset)"
// once use defaultCharset from Java 1.5 in charset init. above.
try {
osw.write(val);
if (val.length() > 0) {
char lastChar = val.charAt(val.length() - 1);
if (lastChar != '\n' && lastChar != '\r') {
osw.write(LS);
}
}
osw.flush();
} finally {
osw.close();
}
// Since opened in overwrite mode, since we didn't exception out,
// we can be confident that we wrote all the bytest in the file.
stdprintln(rb.getString(SqltoolRB.FILE_WROTECHARS,
Long.toString(dumpFile.length()), dumpFile.toString()));
}
byte[] binBuffer = null;
/**
* Binary file dump
*
* dumpFile must not be null.
*/
private void dump(File dumpFile) throws IOException, BadSpecial {
if (binBuffer == null) {
throw new BadSpecial(rb.getString(SqltoolRB.BINBUFFER_EMPTY));
}
FileOutputStream fos = new FileOutputStream(dumpFile);
int len = 0;
try {
fos.write(binBuffer);
len = binBuffer.length;
binBuffer = null;
fos.flush();
} finally {
fos.close();
}
stdprintln(rb.getString(SqltoolRB.FILE_WROTECHARS,
len, dumpFile.toString()));
}
/**
* As the name says...
* This method always closes the input stream.
*/
public String streamToString(InputStream is, String cs)
throws IOException {
try {
byte[] ba = null;
int bytesread = 0;
int retval;
try {
ba = new byte[is.available()];
} catch (RuntimeException re) {
throw new IOException(rb.getString(SqltoolRB.READ_TOOBIG));
}
while (bytesread < ba.length &&
(retval = is.read(
ba, bytesread, ba.length - bytesread)) > 0) {
bytesread += retval;
}
if (bytesread != ba.length) {
throw new IOException(rb.getString(SqltoolRB.READ_PARTIAL,
bytesread,
ba.length));
}
try {
return (cs == null) ? (new String(ba))
: (new String(ba, cs));
} catch (UnsupportedEncodingException uee) {
throw new RuntimeException(uee);
} catch (RuntimeException re) {
throw new IOException(rb.getString(SqltoolRB.READ_CONVERTFAIL));
}
} finally {
is.close();
}
}
/**
* Ascii file load.
*/
private void load(String varName, File asciiFile, String cs)
throws IOException {
String string = streamToString(new FileInputStream(asciiFile), cs);
userVars.put(varName, string);
updateUserSettings();
}
static public byte[] streamToBytes(InputStream is) throws IOException {
byte[] xferBuffer = new byte[10240];
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int i;
while ((i = is.read(xferBuffer)) > 0) {
baos.write(xferBuffer, 0, i);
}
return baos.toByteArray();
}
/**
* Binary file load
*/
static public byte[] loadBinary(File binFile) throws IOException {
byte[] xferBuffer = new byte[10240];
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int i;
FileInputStream fis = new FileInputStream(binFile);
try {
while ((i = fis.read(xferBuffer)) > 0) {
baos.write(xferBuffer, 0, i);
}
} finally {
fis.close();
}
byte[] ba = baos.toByteArray();
return ba;
}
/**
* This method is used to tell SqlFile whether this Sql Type must
* ALWAYS be loaded to the binary buffer without displaying.
*
* N.b.: If this returns "true" for a type, then the user can never
* "see" values for these columns.
* Therefore, if a type may-or-may-not-be displayable, better to return
* false here and let the user choose.
* In general, if there is a toString() operator for this Sql Type
* then return false, since the JDBC driver should know how to make the
* value displayable.
*
* The table on this page lists the most common SqlTypes, all of which
* must implement toString():
* http://java.sun.com/docs/books/tutorial/jdbc/basics/retrieving.html
*
* @see java.sql.Types
*/
public static boolean canDisplayType(int i) {
/* I don't now about some of the more obscure types, like REF and
* DATALINK */
switch (i) {
//case java.sql.Types.BINARY :
case java.sql.Types.BLOB :
case java.sql.Types.JAVA_OBJECT :
//case java.sql.Types.LONGVARBINARY :
//case java.sql.Types.LONGVARCHAR :
case java.sql.Types.OTHER :
case java.sql.Types.STRUCT :
//case java.sql.Types.VARBINARY :
return false;
}
return true;
}
// won't compile with JDK 1.3 without these
private static final int JDBC3_BOOLEAN = 16;
private static final int JDBC3_DATALINK = 70;
public static String sqlTypeToString(int i) {
switch (i) {
case java.sql.Types.ARRAY :
return "ARRAY";
case java.sql.Types.BIGINT :
return "BIGINT";
case java.sql.Types.BINARY :
return "BINARY";
case java.sql.Types.BIT :
return "BIT";
case java.sql.Types.BLOB :
return "BLOB";
case JDBC3_BOOLEAN :
return "BOOLEAN";
case java.sql.Types.CHAR :
return "CHAR";
case java.sql.Types.CLOB :
return "CLOB";
case JDBC3_DATALINK :
return "DATALINK";
case java.sql.Types.DATE :
return "DATE";
case java.sql.Types.DECIMAL :
return "DECIMAL";
case java.sql.Types.DISTINCT :
return "DISTINCT";
case java.sql.Types.DOUBLE :
return "DOUBLE";
case java.sql.Types.FLOAT :
return "FLOAT";
case java.sql.Types.INTEGER :
return "INTEGER";
case java.sql.Types.JAVA_OBJECT :
return "JAVA_OBJECT";
case java.sql.Types.LONGVARBINARY :
return "LONGVARBINARY";
case java.sql.Types.LONGVARCHAR :
return "LONGVARCHAR";
case java.sql.Types.NULL :
return "NULL";
case java.sql.Types.NUMERIC :
return "NUMERIC";
case java.sql.Types.OTHER :
return "OTHER";
case java.sql.Types.REAL :
return "REAL";
case java.sql.Types.REF :
return "REF";
case java.sql.Types.SMALLINT :
return "SMALLINT";
case java.sql.Types.STRUCT :
return "STRUCT";
case java.sql.Types.TIME :
return "TIME";
case java.sql.Types.TIMESTAMP :
return "TIMESTAMP";
case java.sql.Types.TINYINT :
return "TINYINT";
case java.sql.Types.VARBINARY :
return "VARBINARY";
case java.sql.Types.VARCHAR :
return "VARCHAR";
}
return "Unknown type " + i;
}
/**
* Validate that String is safe to display in a DSV file.
*
* @throws SqlToolError if validation fails.
*/
public void dsvSafe(String s) throws SqlToolError {
if (pwDsv == null || dsvColDelim == null || dsvRowDelim == null
|| nullRepToken == null) {
throw new RuntimeException(
"Assertion failed. \n"
+ "dsvSafe called when DSV settings are incomplete");
}
if (s == null) {
return;
}
if (s.indexOf(dsvColDelim) > 0) {
throw new SqlToolError(rb.getString(SqltoolRB.DSV_COLDELIM_PRESENT,
dsvColDelim));
}
if (s.indexOf(dsvRowDelim) > 0) {
throw new SqlToolError(rb.getString(SqltoolRB.DSV_ROWDELIM_PRESENT,
dsvRowDelim));
}
if (s.trim().equals(nullRepToken)) {
// The trim() is to avoid the situation where the contents of a
// field "looks like" the null-rep token.
throw new SqlToolError(rb.getString(SqltoolRB.DSV_NULLREP_PRESENT,
nullRepToken));
}
}
/**
* Translates user-supplied escapes into the traditionaly corresponding
* corresponding binary characters.
*
* Allowed sequences:
* <UL>
* <LI>\0\d+ (an octal digit)
* <LI>\[0-9]\d* (a decimal digit)
* <LI>\[Xx][0-9]{2} (a hex digit)
* <LI>\n Newline (Ctrl-J)
* <LI>\r Carriage return (Ctrl-M)
* <LI>\t Horizontal tab (Ctrl-I)
* <LI>\f Form feed (Ctrl-L)
* </UL>
*
* Java 1.4 String methods will make this into a 1 or 2 line task.
*/
public static String convertEscapes(String inString) {
if (inString == null) {
return null;
}
return convertNumericEscapes(
convertEscapes(convertEscapes(convertEscapes(convertEscapes(
convertEscapes(inString, "\\n", "\n"), "\\r", "\r"),
"\\t", "\t"), "\\\\", "\\"),
"\\f", "\f")
);
}
/**
* @param string Non-null String to modify.
*/
private static String convertNumericEscapes(String string) {
String workString = string;
int i = 0;
for (char dig = '0'; dig <= '9'; dig++) {
while ((i = workString.indexOf("\\" + dig, i)) > -1
&& i < workString.length() - 1) {
workString = convertNumericEscape(string, i);
}
while ((i = workString.indexOf("\\x" + dig, i)) > -1
&& i < workString.length() - 1) {
workString = convertNumericEscape(string, i);
}
while ((i = workString.indexOf("\\X" + dig, i)) > -1
&& i < workString.length() - 1) {
workString = convertNumericEscape(string, i);
}
}
return workString;
}
/**
* @offset Position of the leading \.
*/
private static String convertNumericEscape(String string, int offset) {
int post = -1;
int firstDigit = -1;
int radix = -1;
if (Character.toUpperCase(string.charAt(offset + 1)) == 'X') {
firstDigit = offset + 2;
radix = 16;
post = firstDigit + 2;
if (post > string.length()) post = string.length();
} else {
firstDigit = offset + 1;
radix = (Character.toUpperCase(string.charAt(firstDigit)) == '0')
? 8 : 10;
for (post = firstDigit + 1; post < string.length()
&& Character.isDigit(string.charAt(post)); post++) ;
}
return string.substring(0, offset) + ((char)
Integer.parseInt(string.substring(firstDigit, post), radix))
+ string.substring(post);
}
/**
* @param string Non-null String to modify.
*/
private static String convertEscapes(String string, String from, String to) {
String workString = string;
int i = 0;
int fromLen = from.length();
while ((i = workString.indexOf(from, i)) > -1
&& i < workString.length() - 1) {
workString = workString.substring(0, i) + to
+ workString.substring(i + fromLen);
}
return workString;
}
/**
* Name is self-explanatory.
*
* If there is user demand, open file in random access mode so don't
* need to load 2 copies of the entire file into memory.
* This will be difficult because can't use standard Java language
* features to search through a character array for multi-character
* substrings.
*
* @throws SqlToolError Would prefer to throw an internal exception,
* but we want this method to have external
* visibility.
*/
public void importDsv(String filePath, String skipPrefix)
throws SqlToolError {
/* To make string comparisons, contains() methods, etc. a little
* simpler and concise, just switch all column names to lower-case.
* This is ok since we acknowledge up from that DSV import/export
* assume no special characters or escaping in column names. */
byte[] bfr = null;
File file = new File(filePath);
SortedMap constColMap = null;
if (dsvConstCols != null) {
// We trim col. names, but not values. Must allow users to
// specify values as spaces, empty string, null.
constColMap = new TreeMap();
String[] constPairs = dsvConstCols.split("\\Q"
+ dsvColDelim + "\\E\\s*");
int firstEq;
String n;
for (int i = 0; i < constPairs.length; i++) {
firstEq = constPairs[i].indexOf('=');
n = constPairs[i].substring(0, firstEq).trim().toLowerCase();
if (n.trim().length() < 1) {
throw new SqlToolError(
rb.getString(SqltoolRB.DSV_CONSTCOLS_NULLCOL));
}
constColMap.put(n, constPairs[i].substring(firstEq + 1));
}
}
Set skipCols = null;
if (dsvSkipCols != null) {
skipCols = new HashSet();
String[] skipColsArray = dsvSkipCols.split("\\s*\\Q"
+ dsvColDelim + "\\E\\s*");
for (int i = 0; i < skipColsArray.length; i++) {
skipCols.add(skipColsArray[i].toLowerCase());
}
}
if (!file.canRead()) {
throw new SqlToolError(rb.getString(SqltoolRB.FILE_READFAIL,
file.toString()));
}
try {
bfr = new byte[(int) file.length()];
} catch (RuntimeException re) {
throw new SqlToolError(rb.getString(SqltoolRB.READ_TOOBIG), re);
}
int bytesread = 0;
int retval;
InputStream is = null;
try {
is = new FileInputStream(file);
while (bytesread < bfr.length &&
(retval = is.read(bfr, bytesread, bfr.length - bytesread))
> 0) {
bytesread += retval;
}
} catch (IOException ioe) {
throw new SqlToolError(ioe);
} finally {
if (is != null) try {
is.close();
} catch (IOException ioe) {
errprintln(rb.getString(SqltoolRB.INPUTFILE_CLOSEFAIL)
+ ": " + ioe);
}
}
if (bytesread != bfr.length) {
throw new SqlToolError(rb.getString(SqltoolRB.READ_PARTIAL,
bytesread, bfr.length));
}
String string = null;
String dateString;
try {
string = ((charset == null)
? (new String(bfr)) : (new String(bfr, charset)));
} catch (UnsupportedEncodingException uee) {
throw new RuntimeException(uee);
} catch (RuntimeException re) {
throw new SqlToolError(rb.getString(SqltoolRB.READ_CONVERTFAIL),
re);
}
List headerList = new ArrayList();
String tableName = dsvTargetTable;
// N.b. ENDs are the index of 1 PAST the current item
int recEnd = -1000; // Recognizable value incase something goes
// horrifically wrong.
int colStart;
int colEnd;
// First read one until we get one header line
int lineCount = 0; // Assume a 1 line header?
int recStart = -1;
String trimmedLine = null;
boolean switching = false;
while (true) {
recStart = (recStart < 0) ? 0 : (recEnd + dsvRowDelim.length());
if (recStart > string.length() - 2) {
throw new SqlToolError(rb.getString(SqltoolRB.DSV_HEADER_NONE));
}
recEnd = string.indexOf(dsvRowDelim, recStart);
lineCount++; // Increment when we have line start and end
if (recEnd < 0) {
// Last line in file. No data records.
recEnd = string.length();
}
trimmedLine = string.substring(recStart, recEnd).trim();
if (trimmedLine.length() < 1
|| (skipPrefix != null
&& trimmedLine.startsWith(skipPrefix))) {
continue;
}
if (trimmedLine.startsWith("targettable=")) {
if (tableName == null) {
tableName = trimmedLine.substring(
"targettable=".length()).trim();
}
continue;
}
if (trimmedLine.equals("headerswitch{")) {
if (tableName == null) {
throw new SqlToolError(rb.getString(
SqltoolRB.DSV_HEADER_NOSWITCHTARG, lineCount));
}
switching = true;
continue;
}
if (trimmedLine.equals("}")) {
throw new SqlToolError(rb.getString(
SqltoolRB.DSV_HEADER_NOSWITCHMATCH, lineCount));
}
if (!switching) {
break;
}
int colonAt = trimmedLine.indexOf(':');
if (colonAt < 1 || colonAt == trimmedLine.length() - 1) {
throw new SqlToolError(rb.getString(
SqltoolRB.DSV_HEADER_NONSWITCHED, lineCount));
}
String matcher = trimmedLine.substring(0, colonAt).trim();
// Need to be sure here that tableName is not null (in
// which case it would be determined later on by the file name).
if (matcher.equals("*") || matcher.equalsIgnoreCase(tableName)){
recStart = 1 + string.indexOf(':', recStart);
break;
}
// Skip non-matched header line
}
String headerLine = string.substring(recStart, recEnd);
colStart = recStart;
colEnd = -1;
String colName;
while (true) {
if (colEnd == recEnd) {
// We processed final column last time through loop
break;
}
colEnd = string.indexOf(dsvColDelim, colStart);
if (colEnd < 0 || colEnd > recEnd) {
colEnd = recEnd;
}
if (colEnd - colStart < 1) {
throw new SqlToolError(rb.getString(
SqltoolRB.DSV_NOCOLHEADER,
headerList.size() + 1, lineCount));
}
colName = string.substring(colStart, colEnd).trim().toLowerCase();
headerList.add(
(colName.equals("-")
|| (skipCols != null
&& skipCols.remove(colName))
|| (constColMap != null
&& constColMap.containsKey(colName))
)
? ((String) null)
: colName);
colStart = colEnd + dsvColDelim.length();
} if (skipCols != null && skipCols.size() > 0) {
throw new SqlToolError(rb.getString(
SqltoolRB.DSV_SKIPCOLS_MISSING, skipCols.toString()));
}
boolean oneCol = false; // At least 1 non-null column
for (int i = 0; i < headerList.size(); i++) {
if (headerList.get(i) != null) {
oneCol = true;
break;
}
}
if (oneCol == false) {
// Difficult call, but I think in any real-world situation, the
// user will want to know if they are inserting records with no
// data from their input file.
throw new SqlToolError(rb.getString(SqltoolRB.DSV_NOCOLSLEFT,
dsvSkipCols));
}
int inputColHeadCount = headerList.size();
if (constColMap != null) {
headerList.addAll(constColMap.keySet());
}
String[] headers = (String[]) headerList.toArray(new String[0]);
// headers contains input headers + all constCols, some of these
// values may be nulls.
if (tableName == null) {
tableName = file.getName();
int i = tableName.lastIndexOf('.');
if (i > 0) {
tableName = tableName.substring(0, i);
}
}
StringBuffer tmpSb = new StringBuffer();
List tmpList = new ArrayList();
int skippers = 0;
for (int i = 0; i < headers.length; i++) {
if (headers[i] == null) {
skippers++;
continue;
}
if (tmpSb.length() > 0) {
tmpSb.append(", ");
}
tmpSb.append(headers[i]);
tmpList.add(headers[i]);
}
boolean[] autonulls = new boolean[headers.length - skippers];
boolean[] parseDate = new boolean[autonulls.length];
boolean[] parseBool = new boolean[autonulls.length];
String[] insertFieldName = (String[]) tmpList.toArray(new String[] {});
// Remember that the headers array has all columns in DSV file,
// even skipped columns.
// The autonulls array only has columns that we will insert into.
StringBuffer sb = new StringBuffer("INSERT INTO " + tableName + " ("
+ tmpSb + ") VALUES (");
StringBuffer typeQuerySb = new StringBuffer("SELECT " + tmpSb
+ " FROM " + tableName + " WHERE 1 = 2");
try {
ResultSetMetaData rsmd = curConn.createStatement().executeQuery(
typeQuerySb.toString()).getMetaData();
if (rsmd.getColumnCount() != autonulls.length) {
throw new SqlToolError(rb.getString(
SqltoolRB.DSV_METADATA_MISMATCH));
// Don't know if it's possible to get here.
// If so, it's probably a SqlTool problem, not a user or
// data problem.
// Should be researched and either return a user-friendly
// message or a RuntimeExceptin.
}
for (int i = 0; i < autonulls.length; i++) {
autonulls[i] = true;
parseDate[i] = false;
parseBool[i] = false;
switch(rsmd.getColumnType(i + 1)) {
case java.sql.Types.BOOLEAN:
parseBool[i] = true;
break;
case java.sql.Types.VARBINARY :
case java.sql.Types.VARCHAR :
case java.sql.Types.ARRAY :
// Guessing at how to handle ARRAY.
case java.sql.Types.BLOB :
case java.sql.Types.CLOB :
case java.sql.Types.LONGVARBINARY :
case java.sql.Types.LONGVARCHAR :
autonulls[i] = false;
// This means to preserve white space and to insert
// "" for "". Otherwise we trim white space and
// insert null for \s*.
break;
case java.sql.Types.DATE:
case java.sql.Types.TIME:
case java.sql.Types.TIMESTAMP:
parseDate[i] = true;
}
}
} catch (SQLException se) {
throw new SqlToolError(rb.getString(
SqltoolRB.QUERY_METADATAFAIL,
typeQuerySb.toString()), se);
}
for (int i = 0; i < autonulls.length; i++) {
if (i > 0) {
sb.append(", ");
}
sb.append('?');
}
// Initialize REJECT file(s)
int rejectCount = 0;
File rejectFile = null;
File rejectReportFile = null;
PrintWriter rejectWriter = null;
PrintWriter rejectReportWriter = null;
if (dsvRejectFile != null) try {
rejectFile = new File(dsvRejectFile);
rejectWriter = new PrintWriter((charset == null)
? (new OutputStreamWriter(new FileOutputStream(rejectFile)))
: (new OutputStreamWriter(new FileOutputStream(rejectFile),
charset)));
// Replace with just "(new FileOutputStream(file), charset)"
// once use defaultCharset from Java 1.5 in charset init.
// above.
rejectWriter.print(headerLine + dsvRowDelim);
} catch (IOException ioe) {
throw new SqlToolError(rb.getString(
SqltoolRB.DSV_REJECTFILE_SETUPFAIL, dsvRejectFile), ioe);
}
if (dsvRejectReport != null) try {
rejectReportFile = new File(dsvRejectReport);
rejectReportWriter = new PrintWriter((charset == null)
? (new OutputStreamWriter(
new FileOutputStream(rejectReportFile)))
: (new OutputStreamWriter(
new FileOutputStream(rejectReportFile), charset)));
// Replace with just "(new FileOutputStream(file), charset)"
// once use defaultCharset from Java 1.5 in charset init.
// above.
rejectReportWriter.println(rb.getString(
SqltoolRB.REJECTREPORT_TOP, new String[] {
(new java.util.Date()).toString(),
file.getPath(),
((rejectFile == null) ? rb.getString(SqltoolRB.NONE)
: rejectFile.getPath()),
((rejectFile == null) ? null : rejectFile.getPath()),
}));
} catch (IOException ioe) {
throw new SqlToolError(rb.getString(
SqltoolRB.DSV_REJECTREPORT_SETUPFAIL, dsvRejectReport),
ioe);
}
int recCount = 0;
int skipCount = 0;
PreparedStatement ps = null;
boolean importAborted = false;
try {
try {
ps = curConn.prepareStatement(sb.toString() + ')');
} catch (SQLException se) {
throw new SqlToolError(rb.getString(
SqltoolRB.INSERTION_PREPAREFAIL, sb.toString()), se);
}
String[] dataVals = new String[autonulls.length];
// Length is number of cols to insert INTO, not nec. # in DSV file.
int readColCount;
int storeColCount;
String currentFieldName = null;
// Insert data rows 1-row-at-a-time
while (true) try { try {
recStart = recEnd + dsvRowDelim.length();
if (recStart >= string.length()) {
break;
}
recEnd = string.indexOf(dsvRowDelim, recStart);
lineCount++; // Increment when we have line start and end
if (recEnd < 0) {
// Last record
recEnd = string.length();
}
trimmedLine = string.substring(recStart, recEnd).trim();
if (trimmedLine.length() < 1) {
continue; // Silently skip blank lines
}
if (skipPrefix != null
&& trimmedLine.startsWith(skipPrefix)) {
skipCount++;
continue;
}
if (switching) {
if (trimmedLine.equals("}")) {
switching = false;
continue;
}
int colonAt = trimmedLine.indexOf(':');
if (colonAt < 1 || colonAt == trimmedLine.length() - 1) {
throw new SqlToolError(rb.getString(
SqltoolRB.DSV_HEADER_MATCHERNONHEAD,
lineCount));
}
continue;
}
// Finally we will attempt to add a record!
recCount++;
// Remember that recCount counts both inserts + rejects
colStart = recStart;
colEnd = -1;
readColCount = 0;
storeColCount = 0;
while (true) {
if (colEnd == recEnd) {
// We processed final column last time through loop
break;
}
colEnd = string.indexOf(dsvColDelim, colStart);
if (colEnd < 0 || colEnd > recEnd) {
colEnd = recEnd;
}
if (readColCount == inputColHeadCount) {
throw new RowError(rb.getString(
SqltoolRB.DSV_COLCOUNT_MISMATCH,
inputColHeadCount, 1 + readColCount));
}
if (headers[readColCount++] != null) {
dataVals[storeColCount++] =
string.substring(colStart, colEnd);
}
colStart = colEnd + dsvColDelim.length();
}
if (readColCount < inputColHeadCount) {
throw new RowError(rb.getString(
SqltoolRB.DSV_COLCOUNT_MISMATCH,
inputColHeadCount, readColCount));
}
/* Already checked for readColCount too high in prev. block */
if (constColMap != null) {
Iterator it = constColMap.values().iterator();
while (it.hasNext()) {
dataVals[storeColCount++] = (String) it.next();
}
}
if (storeColCount != dataVals.length) {
throw new RowError(rb.getString(
SqltoolRB.DSV_INSERTCOL_MISMATCH,
dataVals.length, storeColCount));
}
for (int i = 0; i < dataVals.length; i++) {
currentFieldName = insertFieldName[i];
if (autonulls[i]) dataVals[i] = dataVals[i].trim();
// N.b. WE SPECIFICALLY DO NOT HANDLE TIMES WITHOUT
// DATES, LIKE "3:14:00", BECAUSE, WHILE THIS MAY BE
// USEFUL AND EFFICIENT, IT IS NOT PORTABLE.
//System.err.println("ps.setString(" + i + ", "
// + dataVals[i] + ')');
if (parseDate[i]) {
if ((dataVals[i].length() < 1 && autonulls[i])
|| dataVals[i].equals(nullRepToken)) {
ps.setTimestamp(i + 1, null);
} else {
dateString = (dataVals[i].indexOf(':') > 0)
? dataVals[i]
: (dataVals[i] + " 0:00:00");
// BEWARE: This may not work for some foreign
// date/time formats.
try {
ps.setTimestamp(i + 1,
java.sql.Timestamp.valueOf(dateString));
} catch (IllegalArgumentException iae) {
throw new RowError(rb.getString(
SqltoolRB.TIME_BAD, dateString), iae);
}
}
} else if (parseBool[i]) {
if ((dataVals[i].length() < 1 && autonulls[i])
|| dataVals[i].equals(nullRepToken)) {
ps.setNull(i + 1, java.sql.Types.BOOLEAN);
} else {
try {
ps.setBoolean(i + 1, Boolean.valueOf(
dataVals[i]).booleanValue());
// Boolean... is equivalent to Java 4's
// Boolean.parseBoolean().
} catch (IllegalArgumentException iae) {
throw new RowError(rb.getString(
SqltoolRB.BOOLEAN_BAD, dataVals[i]),
iae);
}
}
} else {
ps.setString(
i + 1,
(((dataVals[i].length() < 1 && autonulls[i])
|| dataVals[i].equals(nullRepToken))
? null
: dataVals[i]));
}
currentFieldName = null;
}
retval = ps.executeUpdate();
if (retval != 1) {
throw new RowError(rb.getString(
SqltoolRB.INPUTREC_MODIFIED, retval));
}
possiblyUncommitteds.set(true);
} catch (SQLException se) {
throw new RowError(null, se);
} } catch (RowError re) {
rejectCount++;
if (rejectWriter != null || rejectReportWriter != null) {
if (rejectWriter != null) {
rejectWriter.print(string.substring(
recStart, recEnd) + dsvRowDelim);
}
if (rejectReportWriter != null) {
genRejectReportRecord(rejectReportWriter,
rejectCount, lineCount,
currentFieldName, re.getMessage(),
re.getCause());
}
} else {
importAborted = true;
throw new SqlToolError(
rb.getString(SqltoolRB.DSV_RECIN_FAIL,
lineCount, currentFieldName)
+ ((re.getMessage() == null)
? "" : (" " + re.getMessage())),
re.getCause());
}
}
} finally {
String summaryString = null;
if (recCount > 0) {
summaryString = rb.getString(SqltoolRB.DSV_IMPORT_SUMMARY,
new String[] {
((skipPrefix == null)
? "" : ("'" + skipPrefix + "'-")),
Integer.toString(skipCount),
Integer.toString(rejectCount),
Integer.toString(recCount - rejectCount),
(importAborted ? "importAborted" : null)
});
stdprintln(summaryString);
}
try {
if (recCount > rejectCount && !curConn.getAutoCommit()) {
stdprintln(rb.getString(SqltoolRB.INSERTIONS_NOTCOMMITTED));
}
} catch (SQLException se) {
stdprintln(rb.getString(SqltoolRB.AUTOCOMMIT_FETCHFAIL));
stdprintln(rb.getString(SqltoolRB.INSERTIONS_NOTCOMMITTED));
// No reason to throw here. If use attempts to use the
// connection for anything significant, we will throw then.
}
if (rejectWriter != null) {
rejectWriter.flush();
rejectWriter.close();
}
if (rejectReportWriter != null && rejectCount > 0) {
rejectReportWriter.println(rb.getString(
SqltoolRB.REJECTREPORT_BOTTOM, summaryString, revnum));
rejectReportWriter.flush();
rejectReportWriter.close();
}
if (rejectCount == 0) {
if (rejectFile != null && rejectFile.exists()
&& !rejectFile.delete())
errprintln(rb.getString(SqltoolRB.DSV_REJECTFILE_PURGEFAIL,
rejectFile.toString()));
if (rejectReportFile != null && !rejectReportFile.delete())
errprintln(rb.getString(
SqltoolRB.DSV_REJECTREPORT_PURGEFAIL,
(rejectFile == null)
? null : rejectFile.toString()));
// These are trivial errors.
}
}
}
public static void appendLine(StringBuffer sb, String s) {
sb.append(s + LS);
}
/**
* Does a poor-man's parse of a MSDOS command line and parses it
* into a WIndows cmd.exe invocation to approximate.
*/
static private String[] genWinArgs(String monolithic) {
List list = new ArrayList();
list.add("cmd.exe");
list.add("/y");
list.add("/c");
Matcher m = wincmdPattern.matcher(monolithic);
String[] internalTokens;
while (m.find()) {
for (int i = 1; i <= m.groupCount(); i++) {
if (m.group(i) == null) continue;
if (m.group(i).length() > 1 && m.group(i).charAt(0) == '"') {
list.add(m.group(i).substring(1, m.group(i).length() - 1));
continue;
}
internalTokens = m.group(i).split("\\s+");
for (int j = 0; j < internalTokens.length; j++)
list.add(internalTokens[j]);
}
}
return (String[]) list.toArray(new String[] {});
}
private void genRejectReportRecord(PrintWriter pw, int rCount,
int lCount, String field, String eMsg, Throwable cause) {
pw.println(rb.getString(SqltoolRB.REJECTREPORT_ROW,
new String[] {
((rCount % 2 == 0) ? "even" : "odd") + "row",
Integer.toString(rCount),
Integer.toString(lCount),
((field == null) ? " " : field),
(((eMsg == null) ? "" : eMsg)
+ ((eMsg == null || cause == null) ? "" : "<HR/>")
+ ((cause == null) ? "" : (
(cause instanceof SQLException
&& cause.getMessage() != null)
? cause.getMessage()
: cause.toString()
)
)
)
}));
}
}