/*
* Copyright (C) 2006 SQL Explorer Development Team
* http://sourceforge.net/projects/eclipsesql
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package net.sourceforge.sqlexplorer.sqlpanel;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import net.sourceforge.sqlexplorer.IConstants;
import net.sourceforge.sqlexplorer.Messages;
import net.sourceforge.sqlexplorer.dbproduct.DatabaseProduct;
import net.sourceforge.sqlexplorer.dbproduct.SQLConnection;
import net.sourceforge.sqlexplorer.dbproduct.Session;
import net.sourceforge.sqlexplorer.parsers.ParserException;
import net.sourceforge.sqlexplorer.parsers.Query;
import net.sourceforge.sqlexplorer.parsers.QueryParser;
import net.sourceforge.sqlexplorer.plugin.SQLExplorerPlugin;
import net.sourceforge.sqlexplorer.plugin.editors.Message;
import net.sourceforge.sqlexplorer.plugin.editors.SQLEditor;
import net.sourceforge.sqlexplorer.sqleditor.results.ResultsTab;
import net.sourceforge.sqlexplorer.util.TextUtil;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Shell;
/**
* Base class for SQL Executions.
*
* The AbstractSQLExecution now operates on a QueryTokenizer directly instead of being passed the
* SQL to execute; this is so that individual queries from a single SQLEditor can run synchronously -
* this is essential for DDL queries.
*
* This has been decoupled slightly from SQLEditor (which is now refactored to include result tabs)
* such that an AbstractSQLExecution can simply be told to run by calling startExecute(). The
* constructor is given the SQLEditor instance that fired it off - and when a results tab is
* required, SQLEditor.createResultsTab() is called to get one. The old _composite and _parentTab
* have been replaced by the accessor methods getParentComposite() and getParentTab() respectively
* which now allocate a tab JIT. The purpose behind this change is that the execution can spark
* off several tabs and add entries to the Messages tab.
*
* @modified John Spackman
*/
public abstract class AbstractSQLExecution extends Job {
// Maximum size of the files used to log queries for debugging
private static final long MAX_DEBUG_LOG_SIZE = 64 * 1024;
// Maximum length of the caption for query results windows when the preference
// IConstants.USE_LONG_CAPTIONS_ON_RESULTS is true
public static final int MAX_CAPTION_LENGTH = 25;
private SQLEditor _editor;
protected Session _session;
protected SQLConnection _connection;
// Query tokenizer to get SQL statements from
private QueryParser queryParser;
/**
* Constructor
* @param _editor The SQLEditor that triggered the execution
* @param statement the SQL to be executed
* @param _session the session
*/
public AbstractSQLExecution(SQLEditor editor, QueryParser queryParser) {
super(Messages.getString("SQLExecution.Progress"));
this._editor = editor;
this._session = editor.getSession();
this.queryParser = queryParser;
}
public IStatus run(IProgressMonitor monitor) {
monitor.setTaskName(Messages.getString("SQLExecution.Progress"));
try {
// Wait until we can get a free connection from the queue
_connection = _session.grabConnection();
// Update status
_editor.getEditorToolBar().refresh();
// Make sure the user hasn't tried to terminate us and then run the SQL
if (!monitor.isCanceled() && _connection != null) {
doExecution(monitor);
checkForMessages(null);
}
} catch (final RuntimeException e) {
errorDialog(Messages.getString("SQLResultsView.Error.Title"), e.getClass().getName() + ":" + e.getMessage());
} catch (final Exception e) {
// only log non-sql errors
if (!(e instanceof java.sql.SQLException || e instanceof InterruptedException))
SQLExplorerPlugin.error("Error executing.", e);
errorDialog(Messages.getString("SQLResultsView.Error.Title"), e.getMessage());
} finally {
if (_connection != null)
_session.releaseConnection(_connection);
_connection = null;
_editor.getEditorToolBar().refresh();
}
return new Status(IStatus.OK, getClass().getName(), IStatus.OK, "OK", null);
}
/**
* Main execution method. Note that this method is called from a background thread
* and therefore many SWT operations will need to be done via Display.[a]syncExec()
* @param monitor
* @throws Exception
*/
protected abstract void doExecution(IProgressMonitor monitor) throws Exception;
/**
* This method will be called from the UI thread when execution is cancelled
* and the tab will be disposed. Do any cleanups required in here. Note that this
* method is called from a background thread and therefore many SWT operations will
* need to be done via Display.[a]syncExec()
* @throws Exception
*/
protected abstract void doStop() throws Exception;
/**
* Creates a new tab for the results in SQLEditor
* @return
*/
protected ResultsTab allocateResultsTab(Query query) {
ResultsTab resultsTab = _editor.createResultsTab(this);
if (resultsTab == null)
return null;
boolean longCaptions = SQLExplorerPlugin.getDefault().getPreferenceStore().getBoolean(IConstants.USE_LONG_CAPTIONS_ON_RESULTS);
if (longCaptions) {
String caption = resultsTab.getTabItem().getText() + " [" + TextUtil.compressWhitespace(query.getQuerySql(), MAX_CAPTION_LENGTH) + "]";
resultsTab.getTabItem().setText(caption);
}
return resultsTab;
}
/**
* Checks the database server for messages
*/
protected boolean checkForMessages(Query query) throws SQLException {
LinkedList<Message> messages = new LinkedList<Message>();
Collection<Message> messagesTmp;
if (query != null) {
messagesTmp = _session.getDatabaseProduct().getErrorMessages(_connection, query);
if (messagesTmp != null)
messages.addAll(messagesTmp);
}
messagesTmp = _session.getDatabaseProduct().getServerMessages(_connection);
if (messagesTmp != null)
messages.addAll(messagesTmp);
boolean hasMessages = false;
for (Message msg : messages) {
msg.setLineNo(getQueryParser().adjustLineNo(msg.getLineNo()));
if (msg.getStatus() != Message.Status.SUCCESS)
hasMessages = true;
}
addMessages(messages);
return hasMessages;
}
/**
* Handles a SQLException by parsing the message and populating the messages tab;
* where error messages from the server are numbered, they start relative to the
* line number of the query that was sent; lineNoOffset is added to each line
* number so that they relate to the line in SQLEditor
* @param e
*/
protected void logException(SQLException e, String sql) throws SQLException {
Collection<Message> messages = _session.getDatabaseProduct().getErrorMessages(_connection, e, 0);
if (messages == null)
return;
for (Message message : messages) {
int lineNo = message.getLineNo();
lineNo = queryParser.adjustLineNo(lineNo);
message.setLineNo(lineNo);
message.setSql(sql);
}
addMessages(messages);
}
/**
* Handles a SQLException by parsing the message and populating the messages tab;
* where error messages from the server are numbered, they start relative to the
* line number of the query that was sent; lineNoOffset is added to each line
* number so that they relate to the line in SQLEditor
* @param e
*/
protected void logException(ParserException e, Query query) throws SQLException {
LinkedList<Message> messages = new LinkedList<Message>();
messages.add(new Message(Message.Status.FAILURE, e.getLineNo(), e.getCharNo(), query.getQuerySql(), e.getMessage()));
addMessages(messages);
}
/**
* Handles a SQLException by parsing the message and populating the messages tab;
* where error messages from the server are numbered, they start relative to the
* line number of the query that was sent; lineNoOffset is added to each line
* number so that they relate to the line in SQLEditor
* @param e The exception
* @param query The Query that triggered the exception
* @param positionEditor Whether to reposition the text caret of the editor to the first message
*/
protected void logException(SQLException e, Query query, boolean positionEditor) throws SQLException {
DatabaseProduct product = _session.getDatabaseProduct();
if (product == null)
return;
final Collection<Message> messages = product.getErrorMessages(_connection, e, query.getLineNo() - 1);
if (messages == null)
return;
for (Message message : messages) {
int lineNo = message.getLineNo();
lineNo = queryParser.adjustLineNo(lineNo);
message.setLineNo(lineNo);
message.setSql(query.getQuerySql());
}
addMessages(messages);
if (positionEditor) {
final Shell shell = getEditor().getSite().getShell();
shell.getDisplay().asyncExec(new Runnable() {
public void run() {
if (messages.size() > 0) {
Message msg = messages.iterator().next();
getEditor().setCursorPosition(msg.getLineNo(), msg.getCharNo());
}
}
});
}
}
/**
* Called to add messages to the message tab
* @param messages a collection of SQLEditor.Message objects
*/
protected void addMessages(final Collection<Message> messages) {
_editor.getSite().getShell().getDisplay().asyncExec(new Runnable() {
public void run() {
Iterator iter = messages.iterator();
while (iter.hasNext()) {
Message message = (Message)iter.next();
_editor.addMessage(message);
}
}
});
}
/**
* Called to add messages to the message tab
* @param messages a collection of SQLEditor.Message objects
*/
protected void addMessage(final Message message) {
_editor.getSite().getShell().getDisplay().asyncExec(new Runnable() {
public void run() {
_editor.addMessage(message);
}
});
}
/**
* Helper method to set the progress message - switches to the UI thread
* @param progressMessage
*/
public final void setMessage(final String message) {
getEditor().getSite().getShell().getDisplay().asyncExec(new Runnable() {
public void run() {
if (getEditor() != null)
getEditor().setMessage(message);
}
});
}
/**
* Helper method to set the progress message - switches to the UI thread
* @param progressMessage
*/
public final void setProgressMessage(final String message) {
}
/**
* Logs the query to the debug log file, but only if the preferences require
* it. If the query failed, the exception should be included too.
* @param query
* @param e
*/
protected void debugLogQuery(Query query, SQLException sqlException) {
// Get the logging level
String level = SQLExplorerPlugin.getDefault().getPreferenceStore().getString(IConstants.QUERY_DEBUG_LOG_LEVEL);
if (level == null || level.equals(IConstants.QUERY_DEBUG_OFF))
return;
if (sqlException == null && level.equals(IConstants.QUERY_DEBUG_FAILED))
return;
// Get the log files; if the current log is too big, retire it
File dir = SQLExplorerPlugin.getDefault().getStateLocation().toFile();
File log = new File(dir.getAbsolutePath() + '/' + "query-debug.log");
File oldLog = new File(dir.getAbsolutePath() + '/' + "query-debug.old.log");
// Too big? Then delete the old and archive the current
if (log.exists() && log.length() > MAX_DEBUG_LOG_SIZE) {
oldLog.delete();
log.renameTo(oldLog);
}
// Copy it to the output
PrintWriter writer = null;
try {
FileWriter fw = new FileWriter(log, true);
writer = new PrintWriter(fw);
try {
writer.write("==============================================\r\n");
StringBuffer sb = new StringBuffer(query.toString());
for (int i = 0; i < sb.length(); i++)
if (sb.charAt(i) == '\n')
sb.insert(i++, '\r');
sb.append("\r\n");
writer.write(sb.toString());
if (sqlException != null)
writer.write("FAILED: " + sqlException.getMessage() + "\r\n");
} finally {
writer.flush();
writer.close();
}
} catch(IOException e) {
SQLExplorerPlugin.error("Failed to log query", e);
}
}
/**
* Helper method that switches to the UI thread and presents an Error dialog
* @param title
* @param message
*/
protected void errorDialog(final String title, final String message) {
final Shell shell = getEditor().getSite().getShell();
shell.getDisplay().asyncExec(new Runnable() {
public void run() {
MessageDialog.openError(shell, title, message);
}
});
}
/**
* @return the _editor
*/
public SQLEditor getEditor() {
return _editor;
}
/**
* @return the queryParser
*/
public QueryParser getQueryParser() {
return queryParser;
}
}