sqlline.SqlLine Maven / Gradle / Ivy
/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde licenses this file to you under the Modified BSD License
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at:
//
// http://opensource.org/licenses/BSD-3-Clause
*/
package sqlline;
import java.io.*;
import java.lang.reflect.*;
import java.net.*;
import java.sql.*;
import java.text.*;
import java.util.*;
import java.util.Date;
import java.util.jar.*;
import jline.*;
import jline.console.ConsoleReader;
import jline.console.UserInterruptException;
import jline.console.completer.Completer;
import jline.console.history.FileHistory;
/**
* A console SQL shell with command completion.
*
* TODO:
*
* - Page results
* - Handle binary data (blob fields)
* - Implement command aliases
* - Stored procedure execution
* - Binding parameters to prepared statements
* - Scripting language
* - XA transactions
*
*/
public class SqlLine {
private static final ResourceBundle RESOURCE_BUNDLE =
ResourceBundle.getBundle(SqlLine.class.getName());
private static final String SEPARATOR = System.getProperty("line.separator");
private boolean exit = false;
private final DatabaseConnections connections = new DatabaseConnections();
public static final String COMMAND_PREFIX = "!";
private Set drivers = null;
private String lastProgress = null;
private final Map seenWarnings =
new HashMap();
private final Commands commands = new Commands(this);
private OutputFile scriptOutputFile = null;
private OutputFile recordOutputFile = null;
private PrintStream outputStream = new PrintStream(System.out, true);
private PrintStream errorStream = new PrintStream(System.err, true);
private ConsoleReader consoleReader;
private List batch = null;
private final Reflector reflector;
private Application application;
private Config appConfig;
// saveDir() is used in various opts that assume it's set. But that means
// properties starting with "sqlline" are read into props in unspecific
// order using reflection to find setter methods. Avoid
// confusion/NullPointer due about order of config by prefixing it.
public static final String SQLLINE_BASE_DIR = "x.sqlline.basedir";
private static boolean initComplete = false;
private SqlLineSignalHandler signalHandler = null;
private final Completer sqlLineCommandCompleter;
static {
String testClass = "jline.console.ConsoleReader";
try {
Class.forName(testClass);
} catch (Throwable t) {
String message =
locStatic(RESOURCE_BUNDLE, System.err, "jline-missing", testClass);
throw new ExceptionInInitializerError(message);
}
}
static Manifest getManifest() throws IOException {
URL base = SqlLine.class.getResource("/META-INF/MANIFEST.MF");
URLConnection c = base.openConnection();
if (c instanceof JarURLConnection) {
return ((JarURLConnection) c).getManifest();
}
return null;
}
static String getManifestAttribute(String name) {
try {
Manifest m = getManifest();
if (m == null) {
return "??";
}
Attributes attrs = m.getAttributes("sqlline");
if (attrs == null) {
return "???";
}
String val = attrs.getValue(name);
if (val == null || "".equals(val)) {
return "????";
}
return val;
} catch (Exception e) {
e.printStackTrace();
return "?????";
}
}
String getApplicationTitle() {
try {
return application.getInfoMessage();
} catch (Exception e) {
handleException(e);
return Application.DEFAULT_APP_INFO_MESSAGE;
}
}
static String getApplicationContactInformation() {
return getManifestAttribute("Implementation-Vendor");
}
String loc(String res, int param) {
try {
return MessageFormat.format(
new ChoiceFormat(RESOURCE_BUNDLE.getString(res)).format(param),
param);
} catch (Exception e) {
return res + ": " + param;
}
}
String loc(String res, Object... params) {
return locStatic(RESOURCE_BUNDLE, getErrorStream(), res, params);
}
static String locStatic(ResourceBundle resourceBundle, PrintStream err,
String res, Object... params) {
try {
return MessageFormat.format(resourceBundle.getString(res), params);
} catch (Exception e) {
e.printStackTrace(err);
try {
return res + ": " + Arrays.toString(params);
} catch (Exception e2) {
return res;
}
}
}
protected String locElapsedTime(long milliseconds) {
return loc("time-ms", milliseconds / 1000d);
}
/**
* Starts the program.
*
* @param args Arguments specified on the command-line
* @throws IOException on error
*/
public static void main(String[] args) throws IOException {
start(args, null, true);
}
/**
* Starts the program with redirected input.
*
* For redirected output, use {@link #setOutputStream} and
* {@link #setErrorStream}.
*
*
Exits with 0 on success, 1 on invalid arguments, and 2 on any
* other error.
*
* @param args same as main()
* @param inputStream redirected input, or null to use standard input
* @return Status code to be returned to the operating system
* @throws IOException on error
*/
public static Status mainWithInputRedirection(String[] args,
InputStream inputStream) throws IOException {
return start(args, inputStream, false);
}
public SqlLine() {
reflector = new Reflector(this);
setAppConfig(new Application());
getOpts().loadProperties(System.getProperties());
sqlLineCommandCompleter = new SqlLineCommandCompleter(this);
// attempt to dynamically load signal handler
try {
Class handlerClass = Class.forName("sqlline.SunSignalHandler");
signalHandler = (SqlLineSignalHandler) handlerClass.newInstance();
} catch (Throwable t) {
handleException(t);
}
}
/**
* Backwards compatibility method to allow
* {@link #mainWithInputRedirection(String[], java.io.InputStream)} proxied
* calls to keep method signature but add in new behavior of not saving
* queries.
*
* @param args args[] passed in directly from {@link #main(String[])}
* @param inputStream Stream to read sql commands from (stdin or a file) or
* null for an interactive shell
* @param saveHistory Whether to save the commands issued to SQLLine's history
* file
*
* @return Whether successful
*
* @throws IOException if SQLLine cannot obtain
* history file or start console reader
*/
public static Status start(String[] args,
InputStream inputStream,
boolean saveHistory) throws IOException {
SqlLine sqlline = new SqlLine();
Status status = sqlline.begin(args, inputStream, saveHistory);
if (!Boolean.getBoolean(SqlLineOpts.PROPERTY_NAME_EXIT)) {
System.exit(status.ordinal());
}
return status;
}
DatabaseConnection getDatabaseConnection() {
return connections.current();
}
Connection getConnection() {
if (getDatabaseConnections().current() == null) {
throw new IllegalArgumentException(loc("no-current-connection"));
}
if (getDatabaseConnections().current().connection == null) {
throw new IllegalArgumentException(loc("no-current-connection"));
}
return getDatabaseConnections().current().connection;
}
DatabaseMetaData getDatabaseMetaData() {
if (getDatabaseConnections().current() == null) {
throw new IllegalArgumentException(loc("no-current-connection"));
}
if (getDatabaseConnections().current().getDatabaseMetaData() == null) {
throw new IllegalArgumentException(loc("no-current-connection"));
}
return connections.current().getDatabaseMetaData();
}
/**
* Entry point to creating a {@link ColorBuffer} with color
* enabled or disabled depending on the value of {@link SqlLineOpts#getColor}.
*/
ColorBuffer getColorBuffer() {
return new ColorBuffer(getOpts().getColor());
}
/**
* Entry point to creating a {@link ColorBuffer} with color enabled or
* disabled depending on the calue of {@link SqlLineOpts#getColor}.
*/
ColorBuffer getColorBuffer(String msg) {
return new ColorBuffer(msg, getOpts().getColor());
}
/**
* Walk through all the known drivers and try to register them.
*/
void registerKnownDrivers() {
for (String driverName : appConfig.knownDrivers) {
try {
Class.forName(driverName);
} catch (Throwable t) {
// ignore
}
}
}
/** Parses arguments.
*
* @param args Command-line arguments
* @param callback Status callback
* @return Whether arguments parsed successfully
*/
Status initArgs(String[] args, DispatchCallback callback) {
List commands = new LinkedList();
List files = new LinkedList();
String driver = null;
String user = null;
String pass = null;
String url = null;
String nickname = null;
String logFile = null;
String commandHandler = null;
String appConfig = null;
for (int i = 0; i < args.length; i++) {
if (args[i].equals("--help") || args[i].equals("-h")) {
return Status.ARGS;
}
// -- arguments are treated as properties
if (args[i].startsWith("--")) {
String[] parts = split(args[i].substring(2), "=");
debug(loc("setting-prop", Arrays.asList(parts)));
if (parts.length > 0) {
boolean ret;
if (parts.length >= 2) {
ret = getOpts().set(parts[0], parts[1], true);
} else {
ret = getOpts().set(parts[0], "true", true);
}
if (!ret) {
return Status.ARGS;
}
}
continue;
}
if (args[i].charAt(0) == '-') {
if (i == args.length - 1) {
return Status.ARGS;
}
if (args[i].equals("-d")) {
driver = args[++i];
} else if (args[i].equals("-ch")) {
commandHandler = args[++i];
} else if (args[i].equals("-n")) {
user = args[++i];
} else if (args[i].equals("-p")) {
pass = args[++i];
} else if (args[i].equals("-u")) {
url = args[++i];
} else if (args[i].equals("-e")) {
commands.add(args[++i]);
} else if (args[i].equals("-f")) {
getOpts().setRun(args[++i]);
} else if (args[i].equals("-log")) {
logFile = args[++i];
} else if (args[i].equals("-nn")) {
nickname = args[++i];
} else if (args[i].equals("-ac")) {
appConfig = args[++i];
} else {
return Status.ARGS;
}
} else {
files.add(args[i]);
}
}
if (appConfig != null) {
dispatch(COMMAND_PREFIX + "appconfig " + appConfig,
new DispatchCallback());
}
if (url != null) {
String com =
COMMAND_PREFIX + "connect "
+ url + " "
+ (user == null || user.length() == 0 ? "''" : user) + " "
+ (pass == null || pass.length() == 0 ? "''" : pass) + " "
+ (driver == null ? "" : driver);
debug("issuing: " + com);
dispatch(com, new DispatchCallback());
}
if (nickname != null) {
dispatch(COMMAND_PREFIX + "nickname " + nickname, new DispatchCallback());
}
if (logFile != null) {
dispatch(COMMAND_PREFIX + "record " + logFile, new DispatchCallback());
}
if (commandHandler != null) {
StringBuilder sb = new StringBuilder();
for (String chElem : commandHandler.split(",")) {
sb.append(chElem).append(" ");
}
dispatch(COMMAND_PREFIX + "commandhandler " + sb.toString(),
new DispatchCallback());
}
// now load properties files
for (String file : files) {
dispatch(COMMAND_PREFIX + "properties " + file, new DispatchCallback());
}
if (commands.size() > 0) {
// for single command execute, disable color
getOpts().setColor(false);
getOpts().setHeaderInterval(-1);
for (String command : commands) {
debug(loc("executing-command", command));
dispatch(command, new DispatchCallback());
}
exit = true; // execute and exit
}
Status status = Status.OK;
// if a script file was specified, run the file and quit
if (getOpts().getRun() != null) {
dispatch(COMMAND_PREFIX + "run \"" + getOpts().getRun() + "\"", callback);
if (callback.isFailure()) {
status = Status.OTHER;
}
dispatch(COMMAND_PREFIX + "quit", new DispatchCallback());
}
return status;
}
/**
* Runs SQLLine, accepting input from the given input stream,
* dispatching it to the appropriate
* {@link CommandHandler} until the global variable exit
is
* true.
*
* Before you invoke this method, you can redirect output by
* calling {@link #setOutputStream(PrintStream)}
* and/or {@link #setErrorStream(PrintStream)}.
*
* @param args Command-line arguments
* @param inputStream Input stream
* @param saveHistory Whether to save the commands issued to SQLLine's history
* file
*
* @return exit status
*
* @throws IOException if SQLLine cannot obtain
* history file or start console reader
*/
public Status begin(String[] args, InputStream inputStream,
boolean saveHistory) throws IOException {
try {
getOpts().load();
} catch (Exception e) {
handleException(e);
}
FileHistory fileHistory =
new FileHistory(new File(getOpts().getHistoryFile()));
ConsoleReader reader;
boolean runningScript = getOpts().getRun() != null;
if (runningScript) {
try {
FileInputStream scriptStream =
new FileInputStream(getOpts().getRun());
reader = getConsoleReader(scriptStream, fileHistory);
} catch (Throwable t) {
handleException(t);
commands.quit(null, new DispatchCallback());
return Status.OTHER;
}
} else {
reader = getConsoleReader(inputStream, fileHistory);
}
final DispatchCallback callback = new DispatchCallback();
Status status = initArgs(args, callback);
switch (status) {
case ARGS:
usage();
// fall through
case OTHER:
return status;
default:
break;
}
try {
info(getApplicationTitle());
} catch (Exception e) {
handleException(e);
}
// basic setup done. From this point on, honor opts value for showing
// exception
initComplete = true;
while (!exit) {
try {
// Execute one instruction; terminate on executing a script if
// there is an error.
signalHandler.setCallback(callback);
dispatch(reader.readLine(getPrompt()), callback);
if (saveHistory) {
fileHistory.flush();
}
if (!callback.isSuccess() && runningScript) {
commands.quit(null, callback);
status = Status.OTHER;
}
} catch (EOFException eof) {
// CTRL-D
commands.quit(null, callback);
} catch (UserInterruptException ioe) {
// CTRL-C
try {
callback.forceKillSqlQuery();
callback.setToCancel();
output(loc("command-canceled"));
} catch (SQLException sqle) {
handleException(sqle);
}
} catch (Throwable t) {
handleException(t);
callback.setToFailure();
}
}
// ### NOTE jvs 10-Aug-2004: Clean up any outstanding
// connections automatically.
// nothing is done with the callback beyond
commands.closeall(null, new DispatchCallback());
if (callback.isFailure()) {
status = Status.OTHER;
}
return status;
}
public ConsoleReader getConsoleReader(InputStream inputStream,
FileHistory fileHistory) throws IOException {
Terminal terminal = TerminalFactory.create();
try {
terminal.init();
} catch (Exception e) {
// For backwards compatibility with code that used to use this lib
// and expected only IOExceptions, convert back to that. We can't
// use IOException(Throwable) constructor, which is only JDK 1.6 and
// later.
final IOException ioException = new IOException(e.toString());
ioException.initCause(e);
throw ioException;
}
if (inputStream != null) {
// ### NOTE: fix for sf.net bug 879425.
consoleReader = new ConsoleReader(inputStream, System.out);
} else {
consoleReader = new ConsoleReader();
}
consoleReader.addCompleter(new SqlLineCompleter(this));
consoleReader.setHistory(fileHistory);
consoleReader.setHandleUserInterrupt(true); // CTRL-C handling
consoleReader.setExpandEvents(false);
return consoleReader;
}
void usage() {
output(loc("cmd-usage"));
}
/**
* Dispatch the specified line to the appropriate {@link CommandHandler}.
*
* @param line The command-line to dispatch
*/
void dispatch(String line, DispatchCallback callback) {
if (line == null) {
// exit
exit = true;
return;
}
if (line.trim().length() == 0) {
callback.setStatus(DispatchCallback.Status.SUCCESS);
return;
}
if (isComment(line)) {
callback.setStatus(DispatchCallback.Status.SUCCESS);
return;
}
line = line.trim();
// save it to the current script, if any
if (scriptOutputFile != null) {
scriptOutputFile.addLine(line);
}
if (isHelpRequest(line)) {
line = COMMAND_PREFIX + "help";
}
if (line.startsWith(COMMAND_PREFIX)) {
Map cmdMap =
new TreeMap();
line = line.substring(1);
for (CommandHandler commandHandler : getCommandHandlers()) {
String match = commandHandler.matches(line);
if (match != null) {
cmdMap.put(match, commandHandler);
}
}
final CommandHandler matchingHandler;
switch (cmdMap.size()) {
case 0:
callback.setStatus(DispatchCallback.Status.FAILURE);
error(loc("unknown-command", line));
return;
case 1:
matchingHandler = cmdMap.values().iterator().next();
break;
default:
// look for the exact match
matchingHandler = cmdMap.get(split(line, 1)[0]);
if (matchingHandler == null) {
callback.setStatus(DispatchCallback.Status.FAILURE);
error(loc("multiple-matches", cmdMap.keySet().toString()));
return;
}
break;
}
callback.setStatus(DispatchCallback.Status.RUNNING);
matchingHandler.execute(line, callback);
} else {
callback.setStatus(DispatchCallback.Status.RUNNING);
commands.sql(line, callback);
}
}
/**
* Test whether a line requires a continuation.
*
* @param line the line to be tested
* @return true if continuation required
*/
boolean needsContinuation(String line) {
if (null == line) {
// happens when CTRL-C used to exit a malformed.
return false;
}
if (isHelpRequest(line)) {
return false;
}
if (line.startsWith(COMMAND_PREFIX)
&& !line.regionMatches(1, "sql", 0, "sql".length())
&& !line.regionMatches(1, "all", 0, "all".length())) {
return false;
}
if (isComment(line)) {
return false;
}
String trimmed = line.trim();
if (trimmed.length() == 0) {
return false;
}
return !trimmed.endsWith(";");
}
/**
* Test whether a line is a help request other than !help.
*
* @param line the line to be tested
* @return true if a help request
*/
boolean isHelpRequest(String line) {
return line.equals("?") || line.equalsIgnoreCase("help");
}
/**
* Test whether a line is a comment.
*
* @param line the line to be tested
* @return true if a comment
*/
boolean isComment(String line) {
// SQL92 comment prefix is "--"
// sqlline also supports shell-style "#" prefix
return line.startsWith("#") || line.startsWith("--");
}
/**
* Print the specified message to the console
*
* @param msg the message to print
*/
public void output(String msg) {
output(msg, true);
}
public void info(String msg) {
if (!getOpts().getSilent()) {
output(msg, true, getErrorStream());
}
}
public void info(ColorBuffer msg) {
if (!getOpts().getSilent()) {
output(msg, true, getErrorStream());
}
}
/**
* Issue the specified error message
*
* @param msg the message to issue
* @return false always
*/
public boolean error(String msg) {
output(getColorBuffer().red(msg), true, errorStream);
return false;
}
public boolean error(Throwable t) {
handleException(t);
return false;
}
public void debug(String msg) {
if (getOpts().getVerbose()) {
output(getColorBuffer().blue(msg), true, errorStream);
}
}
public void output(ColorBuffer msg) {
output(msg, true);
}
public void output(String msg, boolean newline, PrintStream out) {
output(getColorBuffer(msg), newline, out);
}
public void output(ColorBuffer msg, boolean newline) {
output(msg, newline, getOutputStream());
}
public void output(ColorBuffer msg, boolean newline, PrintStream out) {
if (newline) {
out.println(msg.getColor());
} else {
out.print(msg.getColor());
}
if (recordOutputFile == null) {
return;
}
// only write to the record file if we are writing a line ...
// otherwise we might get garbage from backspaces and such.
if (newline) {
recordOutputFile.addLine(msg.getMono()); // always just write mono
}
}
/**
* Print the specified message to the console
*
* @param msg the message to print
* @param newline if false, do not append a newline
*/
public void output(String msg, boolean newline) {
output(getColorBuffer(msg), newline);
}
void autocommitStatus(Connection c) throws SQLException {
debug(loc("autocommit-status", c.getAutoCommit() + ""));
}
/**
* Ensure that autocommit is on for the current connection
*
* @return true if autocommit is set
*/
boolean assertAutoCommit() {
if (!assertConnection()) {
return false;
}
try {
if (getDatabaseConnection().connection.getAutoCommit()) {
return error(loc("autocommit-needs-off"));
}
} catch (Exception e) {
return error(e);
}
return true;
}
/**
* Assert that we have an active, living connection. Print an error message
* if we do not.
*
* @return true if there is a current, active connection
*/
boolean assertConnection() {
try {
if (getDatabaseConnection() == null
|| getDatabaseConnection().connection == null) {
return error(loc("no-current-connection"));
}
if (getDatabaseConnection().connection.isClosed()) {
return error(loc("connection-is-closed"));
}
} catch (SQLException sqle) {
return error(loc("no-current-connection"));
}
return true;
}
/**
* Print out any warnings that exist for the current connection.
*/
void showWarnings() {
if (getDatabaseConnection().connection == null) {
return;
}
if (!getOpts().getShowWarnings()) {
return;
}
try {
showWarnings(getDatabaseConnection().connection.getWarnings());
} catch (Exception e) {
handleException(e);
}
}
/**
* Print the specified warning on the console, as well as any warnings that
* are returned from {@link SQLWarning#getNextWarning}.
*
* @param warn the {@link SQLWarning} to print
*/
void showWarnings(SQLWarning warn) {
if (warn == null) {
return;
}
if (seenWarnings.get(warn) == null) {
// don't re-display warnings we have already seen
seenWarnings.put(warn, new java.util.Date());
handleSQLException(warn);
}
SQLWarning next = warn.getNextWarning();
if (next != warn) {
showWarnings(next);
}
}
String getPrompt() {
DatabaseConnection dbc = getDatabaseConnection();
if (dbc == null || dbc.getUrl() == null) {
return "sqlline> ";
} else if (dbc.getNickname() != null) {
return getPrompt(connections.getIndex() + ": "
+ dbc.getNickname()) + "> ";
} else {
return getPrompt(connections.getIndex() + ": " + dbc.getUrl()) + "> ";
}
}
static String getPrompt(String url) {
if (url == null || url.length() == 0) {
url = "sqlline";
}
if (url.contains(";")) {
url = url.substring(0, url.indexOf(";"));
}
if (url.contains("?")) {
url = url.substring(0, url.indexOf("?"));
}
if (url.length() > 45) {
url = url.substring(0, 45);
}
return url;
}
/**
* Try to obtain the current size of the specified {@link ResultSet} by
* jumping to the last row and getting the row number.
*
* @param rs the {@link ResultSet} to get the size for
* @return the size, or -1 if it could not be obtained
*/
int getSize(ResultSet rs) {
try {
if (rs.getType() == ResultSet.TYPE_FORWARD_ONLY) {
return -1;
}
rs.last();
int total = rs.getRow();
rs.beforeFirst();
return total;
} catch (SQLException sqle) {
return -1;
} catch (AbstractMethodError ame) {
// JDBC 1 driver error
return -1;
}
}
ResultSet getColumns(String table) throws SQLException {
if (!assertConnection()) {
return null;
}
return getDatabaseConnection().meta.getColumns(
getDatabaseConnection().meta.getConnection().getCatalog(),
null,
table,
"%");
}
ResultSet getTables() throws SQLException {
if (!assertConnection()) {
return null;
}
return getDatabaseConnection().meta.getTables(
getDatabaseConnection().meta.getConnection().getCatalog(),
null,
"%",
new String[] {"TABLE"});
}
Set getColumnNames(DatabaseMetaData meta) throws SQLException {
Set names = new HashSet();
info(loc("building-tables"));
try {
ResultSet columns = getColumns("%");
try {
int total = getSize(columns);
int index = 0;
while (columns.next()) {
// add the following strings:
// 1. column name
// 2. table name
// 3. tablename.columnname
progress(index++, total);
final String tableName = columns.getString("TABLE_NAME");
final String columnName = columns.getString("COLUMN_NAME");
names.add(tableName);
names.add(columnName);
names.add(tableName + "." + columnName);
}
progress(index, index);
} finally {
columns.close();
}
info(loc("done"));
return names;
} catch (Throwable t) {
handleException(t);
return Collections.emptySet();
}
}
/**
* Splits the line into an array, tokenizing on space characters.
*
* @param line the line to break up
* @return an array of individual words
*/
String[] split(String line) {
return split(line, 0);
}
/**
* Splits the line into an array, tokenizing on space characters,
* limiting the number of words to read.
*
* @param line the line to break up
* @param limit the limit for number of tokens
* to be processed (0 means no limit)
* @return an array of individual words
*/
String[] split(String line, int limit) {
return split(line, " ", limit);
}
/**
* Splits the line into an array of possibly-compound identifiers, observing
* the database's quoting syntax.
*
* For example, on Oracle, which uses double-quote (") as quote
* character,
*
* !tables "My Schema"."My Table"
*
* returns
*
* { {"!tables"}, {"My Schema", "My Table"} }
*
* @param line the line to break up
* @return an array of compound words
*/
public String[][] splitCompound(String line) {
final DatabaseConnection databaseConnection = getDatabaseConnection();
final Quoting quoting;
if (databaseConnection == null) {
quoting = Quoting.DEFAULT;
} else {
quoting = databaseConnection.quoting;
}
int state = SPACE;
int idStart = -1;
final char[] chars = line.toCharArray();
int n = chars.length;
// Trim off trailing semicolon and/or whitespace
while (n > 0
&& (Character.isWhitespace(chars[n - 1])
|| chars[n - 1] == ';')) {
--n;
}
final List words = new ArrayList();
final List current = new ArrayList();
for (int i = 0; i < n;) {
char c = chars[i];
switch (state) {
case SPACE:
case DOT_SPACE:
++i;
if (Character.isWhitespace(c)) {
// nothing
} else if (c == '.') {
state = DOT_SPACE;
} else if (c == quoting.start) {
if (state == SPACE) {
if (current.size() > 0) {
words.add(
current.toArray(new String[current.size()]));
current.clear();
}
}
state = QUOTED;
idStart = i;
} else {
if (state == SPACE) {
if (current.size() > 0) {
words.add(
current.toArray(new String[current.size()]));
current.clear();
}
}
state = UNQUOTED;
idStart = i - 1;
}
break;
case QUOTED:
++i;
if (c == quoting.end) {
if (i < n
&& chars[i] == quoting.end) {
// Repeated quote character inside a quoted identifier.
// Eliminate one of the repeats, and we remain inside a
// quoted identifier.
System.arraycopy(chars, i, chars, i - 1, n - i);
--n;
} else {
state = SPACE;
final String word =
new String(chars, idStart, i - idStart - 1);
current.add(word);
}
}
break;
case UNQUOTED:
// We are in an unquoted identifier. Whitespace or dot ends
// the identifier, anything else extends it.
++i;
if (Character.isWhitespace(c) || c == '.') {
String word = new String(chars, idStart, i - idStart - 1);
if (word.equalsIgnoreCase("NULL")) {
word = null;
} else if (quoting.upper) {
word = word.toUpperCase();
}
current.add(word);
state = c == '.' ? DOT_SPACE : SPACE;
}
break;
default:
throw new AssertionError("unexpected state " + state);
}
}
switch (state) {
case SPACE:
case DOT_SPACE:
break;
case QUOTED:
case UNQUOTED:
// In the middle of a quoted string. Be lenient, and complete the
// word.
String word = new String(chars, idStart, n - idStart);
if (state == UNQUOTED) {
if (word.equalsIgnoreCase("NULL")) {
word = null;
} else if (quoting.upper) {
word = word.toUpperCase();
}
}
current.add(word);
break;
default:
throw new AssertionError("unexpected state " + state);
}
if (current.size() > 0) {
words.add(current.toArray(new String[current.size()]));
}
return words.toArray(new String[words.size()][]);
}
/**
* In a region of whitespace.
*/
private static final int SPACE = 0;
/**
* In a region of whitespace that contains a dot.
*/
private static final int DOT_SPACE = 1;
/**
* Inside a quoted identifier.
*/
private static final int QUOTED = 2;
/**
* Inside an unquoted identifier.
*/
private static final int UNQUOTED = 3;
String dequote(String str) {
if (str == null) {
return null;
}
if ((str.length() == 1 && (str.charAt(0) == '\'' || str.charAt(0) == '\"'))
|| ((str.charAt(0) == '"' || str.charAt(0) == '\''
|| str.charAt(str.length() - 1) == '"'
|| str.charAt(str.length() - 1) == '\'')
&& str.charAt(0) != str.charAt(str.length() - 1))) {
throw new IllegalArgumentException(
"A quote should be closed for <" + str + ">");
}
char prevQuote = 0;
int index = 0;
while ((str.charAt(index) == str.charAt(str.length() - index - 1))
&& (str.charAt(index) == '"' || str.charAt(index) == '\'')) {
// if start and end point to the same element
if (index == str.length() - index - 1) {
if (prevQuote == str.charAt(index)) {
throw new IllegalArgumentException(
"A non-paired quote may not occur between the same quotes");
} else {
break;
}
// else if start and end point to neighbour elements
} else if (index == str.length() - index - 2) {
index++;
break;
}
prevQuote = str.charAt(index);
index++;
}
return index == 0 ? str : str.substring(index, str.length() - index);
}
String[] split(String line, String delim) {
return split(line, delim, 0);
}
public String[] split(String line, String delim, int limit) {
if (delim.indexOf('\'') != -1 || delim.indexOf('"') != -1) {
// quotes in delim are not supported yet
throw new UnsupportedOperationException();
}
boolean inQuotes = false;
int tokenStart = 0;
int lastProcessedIndex = 0;
List tokens = new ArrayList();
for (int i = 0; i < line.length(); i++) {
if (limit > 0 && tokens.size() == limit) {
break;
}
if (line.charAt(i) == '\'' || line.charAt(i) == '"') {
if (inQuotes) {
if (line.charAt(tokenStart) == line.charAt(i)) {
inQuotes = false;
tokens.add(line.substring(tokenStart, i + 1));
lastProcessedIndex = i;
}
} else {
tokenStart = i;
inQuotes = true;
}
} else if (line.regionMatches(i, delim, 0, delim.length())) {
if (inQuotes) {
i += delim.length() - 1;
continue;
} else if (i > 0 && (
!line.regionMatches(i - delim.length(), delim, 0, delim.length())
&& line.charAt(i - 1) != '\''
&& line.charAt(i - 1) != '"')) {
tokens.add(line.substring(tokenStart, i));
lastProcessedIndex = i;
i += delim.length() - 1;
}
} else if (i > 0
&& line.regionMatches(i - delim.length(), delim, 0, delim.length())) {
if (inQuotes) {
continue;
}
tokenStart = i;
}
}
if ((lastProcessedIndex != line.length() - 1
&& (limit == 0 || limit > tokens.size()))
|| (lastProcessedIndex == 0 && line.length() == 1)) {
tokens.add(line.substring(tokenStart, line.length()));
}
String[] ret = new String[tokens.size()];
for (int i = 0; i < tokens.size(); i++) {
ret[i] = dequote(tokens.get(i));
}
return ret;
}
static Map map(K key, V value, Object... obs) {
final Map m = new HashMap();
m.put(key, value);
for (int i = 0; i < obs.length - 1; i += 2) {
//noinspection unchecked
m.put((K) obs[i], (V) obs[i + 1]);
}
return m;
}
static boolean getMoreResults(Statement stmnt) {
try {
return stmnt.getMoreResults();
} catch (Throwable t) {
return false;
}
}
static String xmlEncode(String str, String charsCouldBeNotEncoded) {
if (str == null) {
return str;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
switch (ch) {
case '"' :
// could be skipped for xml attribute in case of single quotes
// could be skipped for element text
if (charsCouldBeNotEncoded.indexOf(ch) == -1) {
sb.append(""");
} else {
sb.append(ch);
}
break;
case '<' :
sb.append("<");
break;
case '&' :
sb.append("&");
break;
case '>' :
// could be skipped for xml attribute and there is no sequence ]]>
// could be skipped for element text and there is no sequence ]]>
if ((i > 1 && str.charAt(i - 1) == ']' && str.charAt(i - 1) == ']')
|| charsCouldBeNotEncoded.indexOf(ch) == -1) {
sb.append(">");
} else {
sb.append(ch);
}
break;
case '\'' :
// could be skipped for xml attribute in case of double quotes
// could be skipped for element text
if (charsCouldBeNotEncoded.indexOf(ch) == -1) {
sb.append("'");
} else {
sb.append(ch);
}
break;
default:
sb.append(ch);
}
}
return sb.toString();
}
/**
* Split the line based on spaces, asserting that the number of words is
* correct.
*
* @param line the line to split
* @param assertLen the number of words to assure
* @param usage the message to output if there are an incorrect number of
* words.
* @return the split lines, or null if the assertion failed.
*/
String[] split(String line, int assertLen, String usage) {
String[] ret = split(line);
if (ret.length != assertLen) {
error(usage);
return null;
}
return ret;
}
/**
* Wrap the specified string by breaking on space characters.
*
* @param toWrap the string to wrap
* @param len the maximum length of any line
* @param start the number of spaces to pad at the beginning of a line
* @return the wrapped string
*/
String wrap(String toWrap, int len, int start) {
StringBuilder buff = new StringBuilder();
StringBuilder line = new StringBuilder();
char[] head = new char[start];
Arrays.fill(head, ' ');
for (StringTokenizer tok = new StringTokenizer(toWrap, " ");
tok.hasMoreTokens();) {
String next = tok.nextToken();
final int x = line.length();
line.append(line.length() == 0 ? "" : " ").append(next);
if (line.length() > len) {
// The line is now too long. Backtrack: remove the last word, start a
// new line containing just that word.
line.setLength(x);
buff.append(line).append(SEPARATOR).append(head);
line.setLength(0);
line.append(next);
}
}
buff.append(line);
return buff.toString();
}
/**
* Output a progress indicator to the console.
*
* @param cur the current progress
* @param max the maximum progress, or -1 if unknown
*/
void progress(int cur, int max) {
StringBuilder out = new StringBuilder();
if (lastProgress != null) {
char[] back = new char[lastProgress.length()];
Arrays.fill(back, '\b');
out.append(back);
}
String progress =
cur + "/" + (max == -1 ? "?" : "" + max) + " "
+ (max == -1 ? "(??%)"
: "(" + cur * 100 / (max == 0 ? 1 : max) + "%)");
if (cur >= max && max != -1) {
progress += " " + loc("done") + SEPARATOR;
lastProgress = null;
} else {
lastProgress = progress;
}
out.append(progress);
getOutputStream().print(out.toString());
getOutputStream().flush();
}
///////////////////////////////
// Exception handling routines
///////////////////////////////
public void handleException(Throwable e) {
while (e instanceof InvocationTargetException) {
e = ((InvocationTargetException) e).getTargetException();
}
if (e instanceof SQLException) {
handleSQLException((SQLException) e);
} else if (e instanceof WrappedSqlException) {
handleSQLException((SQLException) e.getCause());
} else if (!initComplete && !getOpts().getVerbose()) {
// all init errors must be verbose
if (e.getMessage() == null) {
error(e.getClass().getName());
} else {
error(e.getMessage());
}
} else {
e.printStackTrace(System.err);
}
}
void handleSQLException(SQLException e) {
// all init errors must be verbose
final boolean showWarnings = !initComplete || getOpts().getShowWarnings();
final boolean verbose = !initComplete || getOpts().getVerbose();
final boolean showNested = !initComplete || getOpts().getShowNestedErrs();
if (e instanceof SQLWarning && !showWarnings) {
return;
}
String type = e instanceof SQLWarning ? loc("Warning") : loc("Error");
error(
loc(e instanceof SQLWarning ? "Warning" : "Error",
new Object[] {
e.getMessage() == null ? "" : e.getMessage().trim(),
e.getSQLState() == null ? "" : e.getSQLState().trim(),
e.getErrorCode()}));
if (verbose) {
e.printStackTrace();
}
if (!showNested) {
return;
}
for (SQLException nested = e.getNextException();
nested != null && nested != e;
nested = nested.getNextException()) {
handleSQLException(nested);
}
}
/** Looks for a driver with a particular URL. Returns the name of the class
* if found, null if not found. */
String scanForDriver(String url) {
try {
// already registered
Driver driver;
if ((driver = findRegisteredDriver(url)) != null) {
return driver.getClass().getCanonicalName();
}
// first try known drivers...
scanDrivers(true);
if ((driver = findRegisteredDriver(url)) != null) {
return driver.getClass().getCanonicalName();
}
// now really scan...
scanDrivers(false);
if ((driver = findRegisteredDriver(url)) != null) {
return driver.getClass().getCanonicalName();
}
return null;
} catch (Exception e) {
debug(e.toString());
return null;
}
}
private Driver findRegisteredDriver(String url) {
for (Enumeration drivers = DriverManager.getDrivers();
drivers.hasMoreElements();) {
Driver driver = drivers.nextElement();
try {
if (driver.acceptsURL(url)) {
return driver;
}
} catch (Exception e) {
// ignore
}
}
return null;
}
Set scanDrivers(String line) throws IOException {
return scanDrivers(false);
}
Set scanDrivers(boolean knownOnly) throws IOException {
long start = System.currentTimeMillis();
Set classNames = new HashSet();
if (!knownOnly) {
classNames.addAll(ClassNameCompleter.getClassNames());
}
classNames.addAll(appConfig.knownDrivers);
Set driverClasses = new HashSet();
for (String className : classNames) {
if (!className.toLowerCase().contains("driver")) {
continue;
}
try {
Class c =
Class.forName(className, false,
Thread.currentThread().getContextClassLoader());
if (!Driver.class.isAssignableFrom(c)) {
continue;
}
if (Modifier.isAbstract(c.getModifiers())) {
continue;
}
// now instantiate and initialize it
driverClasses.add((Driver) c.newInstance());
} catch (Throwable t) {
// ignore
}
}
long end = System.currentTimeMillis();
info("scan complete in " + (end - start) + "ms");
return driverClasses;
}
///////////////////////////////////////
// ResultSet output formatting classes
///////////////////////////////////////
int print(ResultSet rs, DispatchCallback callback) throws SQLException {
String format = getOpts().getOutputFormat();
OutputFormat f = getOutputFormats().get(format);
if ("csv".equals(format)) {
final SeparatedValuesOutputFormat csvOutput =
(SeparatedValuesOutputFormat) f;
if ((csvOutput.separator == null && getOpts().getCsvDelimiter() != null)
|| (csvOutput.separator != null
&& !csvOutput.separator.equals(getOpts().getCsvDelimiter())
|| csvOutput.quoteCharacter
!= getOpts().getCsvQuoteCharacter())) {
f = new SeparatedValuesOutputFormat(this,
getOpts().getCsvDelimiter(), getOpts().getCsvQuoteCharacter());
Map updFormats =
new HashMap(getOutputFormats());
updFormats.put("csv", f);
updateOutputFormats(updFormats);
}
}
if (f == null) {
error(loc("unknown-format", format, getOutputFormats().keySet()));
f = new TableOutputFormat(this);
}
Rows rows;
if (getOpts().getIncremental()) {
rows = new IncrementalRows(this, rs, callback);
} else {
rows = new BufferedRows(this, rs);
}
return f.print(rows);
}
Statement createStatement() throws SQLException {
Statement stmnt = getDatabaseConnection().connection.createStatement();
if (getOpts().timeout > -1) {
stmnt.setQueryTimeout(getOpts().timeout);
}
if (getOpts().rowLimit != 0) {
stmnt.setMaxRows(getOpts().rowLimit);
}
return stmnt;
}
void runBatch(List statements) {
try {
Statement stmnt = createStatement();
try {
for (String statement : statements) {
stmnt.addBatch(statement);
}
int[] counts = stmnt.executeBatch();
if (counts == null) {
counts = new int[0];
}
output(
getColorBuffer()
.pad(getColorBuffer().bold("COUNT"), 8)
.append(getColorBuffer().bold("STATEMENT")));
for (int i = 0; i < counts.length; i++) {
output(
getColorBuffer().pad(counts[i] + "", 8)
.append(statements.get(i)));
}
} finally {
try {
stmnt.close();
} catch (Exception e) {
// ignore
}
}
} catch (Exception e) {
handleException(e);
}
}
public int runCommands(List cmds, DispatchCallback callback) {
int successCount = 0;
try {
int index = 1;
int size = cmds.size();
for (String cmd : cmds) {
info(getColorBuffer().pad(index++ + "/" + size, 13).append(cmd));
dispatch(cmd, callback);
boolean success = callback.isSuccess();
// if we do not force script execution, abort
// when a failure occurs.
if (!success && !getOpts().getForce()) {
error(loc("abort-on-error", cmd));
return successCount;
}
successCount += success ? 1 : 0;
}
} catch (Exception e) {
handleException(e);
}
return successCount;
}
void setCompletions() throws SQLException, IOException {
if (getDatabaseConnection() != null) {
getDatabaseConnection().setCompletions(getOpts().getFastConnect());
}
}
public SqlLineOpts getOpts() {
return appConfig.opts;
}
DatabaseConnections getDatabaseConnections() {
return connections;
}
public boolean isExit() {
return exit;
}
public void setExit(boolean exit) {
this.exit = exit;
}
Set getDrivers() {
return drivers;
}
void setDrivers(Set drivers) {
this.drivers = drivers;
}
public static String getSeparator() {
return SEPARATOR;
}
Commands getCommands() {
return commands;
}
OutputFile getScriptOutputFile() {
return scriptOutputFile;
}
void setScriptOutputFile(OutputFile script) {
this.scriptOutputFile = script;
}
OutputFile getRecordOutputFile() {
return recordOutputFile;
}
void setRecordOutputFile(OutputFile record) {
this.recordOutputFile = record;
}
public void setOutputStream(PrintStream outputStream) {
this.outputStream = new PrintStream(outputStream, true);
}
PrintStream getOutputStream() {
return outputStream;
}
public void setErrorStream(PrintStream errorStream) {
this.errorStream = new PrintStream(errorStream, true);
}
PrintStream getErrorStream() {
return errorStream;
}
ConsoleReader getConsoleReader() {
return consoleReader;
}
void setConsoleReader(ConsoleReader reader) {
this.consoleReader = reader;
}
List getBatch() {
return batch;
}
void setBatch(List batch) {
this.batch = batch;
}
public Reflector getReflector() {
return reflector;
}
public Completer getCommandCompleter() {
return sqlLineCommandCompleter;
}
void setAppConfig(Application application) {
this.application = application;
this.appConfig = new Config(application);
}
public Collection getCommandHandlers() {
return appConfig.commandHandlers;
}
public void updateCommandHandlers(
Collection commandHandlers) {
appConfig = appConfig.withCommandHandlers(commandHandlers);
}
public Map getOutputFormats() {
return appConfig.formats;
}
public void updateOutputFormats(Map formats) {
appConfig = appConfig.withFormats(formats);
}
/** Exit status returned to the operating system. OK, ARGS, OTHER
* correspond to 0, 1, 2. */
public enum Status {
OK, ARGS, OTHER
}
/** Cache of configuration settings that come from
* {@link Application}. */
private class Config {
final Collection knownDrivers;
final SqlLineOpts opts;
final Collection commandHandlers;
final Map formats;
Config(Application application) {
this(application.initDrivers(),
application.getOpts(SqlLine.this),
application.getCommandHandlers(SqlLine.this),
application.getOutputFormats(SqlLine.this));
}
Config(Collection knownDrivers,
SqlLineOpts opts,
Collection commandHandlers,
Map formats) {
this.knownDrivers = Collections.unmodifiableSet(
new HashSet(knownDrivers));
this.opts = opts;
this.commandHandlers = Collections.unmodifiableList(
new ArrayList(commandHandlers));
this.formats = Collections.unmodifiableMap(formats);
}
Config withCommandHandlers(Collection commandHandlers) {
return new Config(this.knownDrivers, opts, commandHandlers, this.formats);
}
Config withFormats(Map formats) {
return new Config(this.knownDrivers, opts, this.commandHandlers, formats);
}
}
}
// End SqlLine.java