/*
* (C) Copyright IBM Corp. 2009
*
* LICENSE: Eclipse Public License v1.0
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.ibm.gaiandb.apps.dashboard;
import java.awt.Color;
import java.awt.Component;
import java.awt.Desktop;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.lang.management.ManagementFactory;
import java.net.URI;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import java.util.regex.Pattern;
import javax.swing.AbstractCellEditor;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import com.ibm.db2j.GaianTable;
import com.ibm.gaiandb.Logger;
import com.ibm.gaiandb.apps.SecurityClientAgent;
import com.ibm.gaiandb.diags.GDBMessages;
public class QueryTab extends Tab {
// Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice.
public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2009";
private static final long serialVersionUID = 3983956371547599148L;
private static final int INPUT_TAB_SIZE = 4;
private static final int COLUMN_PADDING = 10;
static final int WARNINGS_COLUMN = 4;
static final String WARNINGS_SQL = "call.*listwarnings\\(\\)";
private Connection conn;
private Statement stmt;
final Vector<String> sqlHistory = new Vector<String>();
private int sqlHistoryIndex = 0;
private final JTextArea input;
private final JTextField reIterateBox;
private final JButton back;
private final JButton forward;
private final JButton submit;
private final JButton lookupError;
private final JButton reconnectButton;
private final JTable results;
private final JLabel queryInfo;
private final JList errors;
private final JComboBox apiCallsDropDown; //, historyDropDown;
private static final String SHORTCUTS_LABEL = "Shortcuts and History...";
private static final String EXPAND_AS_DIRECTED_SUBQ =
"<EXPAND CURRENT QUERY AS DIRECTED SUB-QUERY ON SELECTED NODES IN NETWORK TOPOLOGY>";
private static final String EXPAND_AS_ADDQUERY_CALL =
"<EXPAND CURRENT QUERY AS CALL TO ADDQUERY STORED PROCEDURE>";
private static final String[] APIS_LIST = new String[] {
"select * from LT0 -- Query sample logical table LT0",
"select * from LT0_P -- Query sample logical table LT0 with provenance",
"select * from LT0_0 -- Query LT0 at depth 0, i.e. federating local data sources only",
"call listnodes() -- List GaianDB nodes in network",
"call listrdbc() -- List JDBC Connections", //. Specify '*' for all nodes or null for local config only",
"call listlts() -- List Logical Tables", //. Specify '*' for all nodes or null for local config only",
"call listltmatches() -- List logical table definition matches",
"call listds() -- List Data Sources", //. Specify '*' for all nodes or null for local config only",
"call listwarnings() -- List latest warnings", //. Specify '*' for all nodes or null for local config only",
"call listconfig() -- List GaianDB Config Properties", //. Specify '*' for all nodes or null for local config only",
"call listspfs() -- List Stored Procedures and Functions",
// "call listapi() -- List GaianDB API",
"call listnet('') -- Show net interface info for ips having the given prefix",
"call listflood() -- Show propagation route of an empty query",
"call listqueries(-1) -- Show all existing queries on nodes out to a given depth (use -1 to query all nodes)", //. Specify -1 for all nodes
"call listexplain('<sql>') -- Show route and cumulative returned row counts for a given query",
"call logtail(null, 100) -- Show last 100 lines gaiandb.log. The 1st arg can be a nodelist or '*' for all nodes",
"call setloglevel('NONE') -- Disable Server Logging",
"call setloglevel('LESS') -- Performance Logging",
"call setloglevel('MORE') -- Verbose Logging",
"call addgateway('<ipaddress>') -- Register a discover gateway IP, i.e. use it as a relay node for discovery",
"call removegateway('<ipaddress>') -- De-register a discovery gateway IP",
"call cancelquery('<queryID>') -- Cancel execution of a query (use listqueries() to resolve a queryID)",
"call gconnect('<cid>', '<ipaddress>') -- Create a 1-way connection (naming its ID) to a GaianDB running at the given IP",
"call gdisconnect('<cid>') -- Disconnect the 1-way connection designated by the connection ID",
"call setrdbc('<cid>', '<driver>', '<url>', '<usr>', '<pwd>') -- Register a new connection id (cid) with the given JDBC connection details",
"call removerdbc('<cid>') -- Remove the given connection ID, clearing all associated JDBC connections",
"call setltforrdbtable('<ltname>', '<cid>', '<tableExpr>') -- Set logical table based on connection id + physical table expression (may include joins + where clause...)",
"call setltforfile('<ltname>', '<filepath>') -- Set logical table based on a text file",
"call setltforexcel('<ltname>', '<spreadsheet_parameters>') -- Set logical table based on an excel spreadsheet",
"call setltfornode('<ltname>', '<nodeid>') -- Set logical table based on its definition on another node",
"call removelt('<ltname>') -- Remove the given logical table and all dependant data sources",
"call setminconnections(<numconnections>) -- Set Number of Sought After Connections",
// "call setdiscoveryhosts('<host1,...>') -- Set List of Hostnames considered for node discovery", -- now deprecated
"call setdiscoveryip('<ipaddress>') -- Set IP broadcast address or multicast group for discovery",
"call setconfigproperty('<key>', '<value>') -- Set a specifc registry property (admin only)",
// "select gkill() ok from sysibm.sysdummy1 -- Kill the local node",
"VALUES CURRENT_USER -- Display current user",
};
private final DefaultTableModel resultsModel;
private final DefaultListModel errorsModel;
public QueryTab(final Dashboard container) {
super(container, new GridBagLayout());
GridBagConstraints labelConstraints = new GridBagConstraints();
labelConstraints.anchor = GridBagConstraints.LINE_START;
labelConstraints.fill = GridBagConstraints.HORIZONTAL;
labelConstraints.gridwidth = 3;
labelConstraints.gridx = 0;
labelConstraints.insets = new Insets(
Dashboard.BORDER_SIZE,
Dashboard.BORDER_SIZE,
0,
Dashboard.BORDER_SIZE);
labelConstraints.weightx = 1;
GridBagConstraints componentConstraints = new GridBagConstraints();
componentConstraints.anchor = GridBagConstraints.LINE_START;
componentConstraints.fill = GridBagConstraints.BOTH;
componentConstraints.gridwidth = 3;
componentConstraints.gridx = 0;
componentConstraints.insets = new Insets(
0,
Dashboard.BORDER_SIZE,
Dashboard.BORDER_SIZE,
Dashboard.BORDER_SIZE);
componentConstraints.weightx = 1;
input = new JTextArea();
input.setFont(new Font("Monospaced", Font.PLAIN, input.getFont().getSize()));
input.setTabSize(INPUT_TAB_SIZE);
input.addKeyListener(new KeyListener() {
public void keyTyped(KeyEvent e) {
if (e.isControlDown() && e.getKeyChar() == '\n') {
submitQuery(input.getText());
}
}
public void keyReleased(KeyEvent e) {}
public void keyPressed(KeyEvent e) {}
});
// Look for icons - if they dont exist use text
if ( 0 < BACK_ICON.getIconHeight() ) {
back = new JButton(BACK_ICON);
back.setDisabledIcon(BACK_DISABLED_ICON);
forward = new JButton(FORWARD_ICON);
forward.setDisabledIcon(FORWARD_DISABLED_ICON);
} else {
back = new JButton("<=");
forward = new JButton("=>");
}
back.setEnabled(false);
back.setMargin(new Insets(1, 1, 1, 1));
back.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
back();
}
});
forward.setEnabled(false);
forward.setMargin(new Insets(1, 1, 1, 1));
forward.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
forward();
}
});
submit = new JButton("Run");
submit.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if ( null != executingSQL ) { // Cancelling/Stopping query
lastCancelTime = System.currentTimeMillis();
submit.setText("Run");
queryInfo.setText("Cancelled at " + new SimpleDateFormat("HH:mm:ss").format(new Date()) + ". ");
executingSQL = null;
if ( null != stmt ) try { stmt.close(); stmt = null; } catch ( Exception ex ) { setErrorsWithCode(GDBMessages.CLIENT_STMT_CLOSE_ERROR, ex); }
return;
}
submitQuery(input.getText());
}
});
resultsModel = new DefaultTableModel();
results = new JTable(resultsModel);
results.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
results.setColumnSelectionAllowed(true);
// results.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // single cell selection
// results.getTableHeader().setReorderingAllowed(true); // column 'drag accross' re-ordering
// results.getTableHeader().addMouseListener( ) ); // attempt to allow sorting on columns
queryInfo = new JLabel("");
errorsModel = new DefaultListModel();
errors = new JList(errorsModel);
lookupError = new JButton("Lookup");
lookupError.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String code = ((String) errorsModel.get(0)).split(" ")[0];
openErrorDoc(code);
}
});
reconnectButton = new JButton("Reconnect");
reconnectButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
container.reconnect();
}
});
reconnectButton.setEnabled(false);
setErrors();
Vector<String> v = new Vector<String>( Arrays.asList( SHORTCUTS_LABEL, "" ) );
v.addAll( Arrays.asList(APIS_LIST) );
v.add( "" );
v.add(EXPAND_AS_DIRECTED_SUBQ);
v.add(EXPAND_AS_ADDQUERY_CALL);
v.add("_____________________________________________________________________________________");
v.add("");
apiCallsDropDown = new JComboBox( v );
apiCallsDropDown.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JComboBox cb = (JComboBox)e.getSource();
String sql = ((String)cb.getSelectedItem()).trim();
if ( 0<sql.length() && !sql.equals(SHORTCUTS_LABEL) && !sql.startsWith("_") ) {
if ( cb.getSelectedIndex() < APIS_LIST.length+2 ) {
int idx = sql.indexOf("--");
if ( -1 < idx ) sql = sql.substring(0, idx);
sql = sql.trim();
} else if ( sql.equals(EXPAND_AS_DIRECTED_SUBQ) ) {
sql = "\t" + input.getText().replaceAll("'", "''").replaceAll("\n", "\n\t");
sql = "SELECT * FROM NEW com.ibm.db2j.GaianQuery('\n" + sql +
"\n', 'with_provenance') GQ";
Set<String> selectedNodes = TopologyGraph.getSingleton().getSelectedNodes();
if ( null != selectedNodes && !selectedNodes.isEmpty() ) {
StringBuffer whereNodes = new StringBuffer(" WHERE GDB_NODE<'!'\n");
int i=0;
for ( String n : selectedNodes ) {
if ( 0 == ++i%3 ) whereNodes.append('\n');
whereNodes.append("OR GDB_NODE='" + n + "' ");
}
sql += whereNodes;
}
} else if ( sql.equals(EXPAND_AS_ADDQUERY_CALL) ) {
sql = input.getText().replace("'", "''");
sql = "call addquery('<id>', '<description>', '<issuer>', '" + sql + "', '<fields>')";
}
input.setText(sql);
input.requestFocus();
}
cb.setSelectedIndex(0);
}
});
apiCallsDropDown.setMaximumRowCount(v.size()+10);
// historyDropDown = new JComboBox();
// historyDropDown.addActionListener(new ActionListener() {
// public void actionPerformed(ActionEvent e) {
// JComboBox cb = (JComboBox)e.getSource();
// String sql = (String)cb.getSelectedItem();
// if ( !sql.equals(CLICK_FOR_API_AND_HISTORY) && 0<sql.length()) {
// input.setText(sql);
// cb.setSelectedIndex(0);
//
// }
// });
int baseSize = getFont().getSize();
GridBagConstraints c;
JPanel p1 = new JPanel(new GridBagLayout()), p2 = new JPanel(new GridBagLayout()), p3 = new JPanel(new GridBagLayout());
labelConstraints.gridy = 0;
p1.add(new JLabel("SQL Query:"), labelConstraints);
c = (GridBagConstraints)componentConstraints.clone();
c.gridwidth = 3;
c.gridheight = 4;
c.gridx = 0;
c.gridy = 1;
c.weighty = 0.15;
p1.add(createScroller(input, -1, baseSize * 6), c);
c = (GridBagConstraints)componentConstraints.clone();
c.gridwidth = 1;
c.fill = GridBagConstraints.BOTH;
c.weightx = 1;
c.gridx = 0;
c.gridy = 5;
p1.add(apiCallsDropDown, c);
// c = (GridBagConstraints)componentConstraints.clone();
// c.gridwidth = 2;
// c.fill = GridBagConstraints.NONE;
// c.weightx = 1;
// c.gridx = 2;
// c.gridy = 3;
// p1.add(historyDropDown, c); //new JLabel("BLAH BLAH BLAH BLAH BLAH BLAH BLAH BLAH BLAH:"), c);
c = (GridBagConstraints)componentConstraints.clone();
c.anchor = GridBagConstraints.PAGE_START;
c.fill = GridBagConstraints.NONE;
c.insets = new Insets(c.insets.top, 0, c.insets.bottom, c.insets.right);
c.gridwidth = 1;
c.gridheight = 1;
c.gridx = 3;
c.gridy = 1;
c.weightx = 0;
c.weighty = 0;
p1.add(back, c);
c.gridx = 4;
c.anchor = GridBagConstraints.EAST;
p1.add(forward, c);
c.weighty = 1;
c.anchor = GridBagConstraints.PAGE_END;
c.fill = GridBagConstraints.BOTH;
c.gridwidth = 2;
c.gridx = 3;
c.gridy = 2;
p1.add(submit, c);
// c = (GridBagConstraints)componentConstraints.clone();
GridBagConstraints c2 = new GridBagConstraints();
c2.gridx = GridBagConstraints.RELATIVE;
c2.gridy = 0;
c2.anchor = GridBagConstraints.NORTH;
c2.fill = GridBagConstraints.HORIZONTAL; //c2.weightx = 1;
JPanel reIteratePanel = new JPanel(new GridBagLayout());
reIterateBox = new JTextField();
reIterateBox.setDocument(Field.getValidatedDocument("[1-9][0-9]*"));
reIterateBox.setText("1");
reIterateBox.setBorder(BorderFactory.createEtchedBorder());
reIteratePanel.add(new JLabel("x "), c2);
c2.weightx = 1;
reIteratePanel.add(reIterateBox, c2);
reIterateBox.addFocusListener(new FocusListener() {
public void focusGained(FocusEvent e) {}
public void focusLost(FocusEvent e) {
JTextField tBox = (JTextField)e.getSource();
if ( 0 == tBox.getText().length() ) tBox.setText("1");
}
});
c.fill = GridBagConstraints.HORIZONTAL; c.weighty = 0;
c.gridx = 3; c.gridy = 3; c.gridwidth = 2;
c.anchor = GridBagConstraints.NORTH;
p1.add(reIteratePanel, c);
labelConstraints.gridy = 3;
p2.add(new JLabel("Results:"), labelConstraints);
c = (GridBagConstraints)componentConstraints.clone();
c.gridy = 4;
c.insets = new Insets(0, Dashboard.BORDER_SIZE, 0, Dashboard.BORDER_SIZE);
c.weighty = 0.75;
p2.add(createScroller(results, -1, baseSize * 10), c);
// resultsModel.setColumnCount(0);
// resultsModel.setRowCount(0);
labelConstraints.gridy = 5;
Insets holding = labelConstraints.insets;
labelConstraints.insets = new Insets(0, Dashboard.BORDER_SIZE, Dashboard.BORDER_SIZE, Dashboard.BORDER_SIZE);
p2.add(queryInfo, labelConstraints);
labelConstraints.insets = holding;
// Final Panel
labelConstraints.gridy = 0;
p3.add(new JLabel("Errors/Warnings:"), labelConstraints);
c = (GridBagConstraints)componentConstraints.clone();
c.fill = GridBagConstraints.BOTH;
c.gridwidth = 3;
c.gridheight = 4;
c.gridy = 1;
c.gridx = 0;
c.weightx = 0.9;
p3.add(createScroller(errors, -1, baseSize * 6), c);
c.weightx = 0;
c.anchor = GridBagConstraints.EAST;
c.gridwidth = 1;
c.gridheight = 1;
c.gridy = 3;
c.gridx = 3;
p3.add(lookupError, c);
c.gridy = 4;
p3.add(reconnectButton, c);
// Add them to the tab
c = (GridBagConstraints)componentConstraints.clone();
c.weighty = 0.8;
c.anchor = GridBagConstraints.PAGE_START;
c.fill = GridBagConstraints.BOTH;
c.gridheight = 2;
c.gridwidth = 1;
c.weightx = 0.2;
this.add(new JSplitPane(JSplitPane.VERTICAL_SPLIT, p1, p2), c);
c.anchor = GridBagConstraints.PAGE_END;
c.weighty = 0.2;
this.add(p3, c);
initialiseResultsTable();
// new Thread( new Runnable() {
// public void run() {
// while ( true ) { try { Thread.sleep(1000); } catch (InterruptedException e) {} System.gc(); } }
// }, "DashGC" ).start();
}
private void initialiseResultsTable() {
// Need to initialise the results table before running queries in a separate thread as the initialisation
// involves synchronized awt code under the covers (e.g. java/awt/Component.setFont(Component.java:1646))
// which sometimes causes a deadlock when accessed concurrently by separate threads.
// Initialise the cell renderer - includes colours and fonts
resultsModel.setColumnIdentifiers(new String[] {""});
resultsModel.addRow(new String[] {""});
results.getCellRenderer(0, 0);
// Clear table again immediately
resultsModel.setColumnCount(0);
resultsModel.setRowCount(0);
}
private void addToSQLHistory( String sql ) {
String last = sqlHistory.isEmpty() ? null : sqlHistory.lastElement();
// If this sql is different to the last issued query (incl if there wasnt one), add the query to the history.
if ( !sql.equals(last) ) {
sqlHistory.add(sql);
boolean isInDropDownHistory = false;
int dropDownItemsCount = apiCallsDropDown.getItemCount();
for ( int i=0; i<dropDownItemsCount; i++ )
if ( ((String)apiCallsDropDown.getItemAt(i)).startsWith(sql) ) {
isInDropDownHistory = true; break;
}
if ( !isInDropDownHistory ) apiCallsDropDown.addItem(sql);
}
sqlHistoryIndex = sqlHistory.size()-1;
if ( 0 < sqlHistoryIndex )
back.setEnabled(true);
forward.setEnabled(false);
}
private String executingSQL = null;
private long lastCancelTime = 0, lastStartTime = 0;
private long previousCellCount = 0;
private boolean memDecreasedSubstantially = false;
protected synchronized void submitQuery(String sql) {
if (null==sql || 0 == sql.trim().length() && null != executingSQL ) return;
if (null == conn) {
JOptionPane.showMessageDialog(
this,
"You must be connected to a GaianDB node in order to execute queries.",
"Not Connected",
JOptionPane.ERROR_MESSAGE);
return;
}
addToSQLHistory(sql);
executingSQL = sql;
new Thread( new Runnable() {
public void run() {
resultsModel.setColumnCount(0);
resultsModel.setRowCount(0);
long startTime = lastStartTime = System.currentTimeMillis();
try {
if ( null == stmt ) stmt = conn.createStatement();
// stmt.setQueryTimeout(Dashboard.QUERY_TIMEOUT); // causes issues if the SQL is an INSERT
ResultSet resultSet = null;
long execTime = 0;
// Clear previous security credentials hint
int credIndex = executingSQL.indexOf("\n-- " + SecurityClientAgent.GDB_CREDENTIALS);
if ( -1 != credIndex ) executingSQL = executingSQL.substring(0, credIndex);
// Insert new credentials value as hint if one or more were specified
if ( container.securityAgent.isSecurityCredentialsSpecified() ) {
container.securityAgent.refreshPublicKeysFromServers(stmt);
executingSQL += "\n-- " + SecurityClientAgent.GDB_CREDENTIALS + "=" +
container.securityAgent.getEncryptedCredentialsValueInBase64(executingSQL) + "\n";
input.setText( executingSQL );
}
String repeatTxt = reIterateBox.getText();
if ( null == repeatTxt || 1 > repeatTxt.length() )
reIterateBox.setText(repeatTxt = "1");
int repeatCount = Integer.parseInt( repeatTxt );
setErrors("");
lookupError.setEnabled(false);
queryInfo.setText("Executing Query, please wait...");
submit.setText("Stop");
long aggregateRowCount = 0;
long totalTime = -System.currentTimeMillis();
int nextLogTimeDurationUnits = 0; // 10ths of a second
int countdown = repeatCount;
while ( 0 < countdown-- ) {
int rowsFetched = 0;
int queryIndex = repeatCount - countdown;
boolean showProgress = nextLogTimeDurationUnits < (System.currentTimeMillis() + totalTime)/100;
if ( showProgress ) {
nextLogTimeDurationUnits++;
queryInfo.setText("Repetition " + queryIndex + ", please wait...");
}
execTime -= System.currentTimeMillis();
stmt.execute(executingSQL);
execTime += System.currentTimeMillis();
if ( startTime < lastCancelTime ) return;
resultSet = stmt.getResultSet();
if (null != resultSet) {
if ( 0 < countdown ) {
while (resultSet.next()) {
if ( startTime < lastCancelTime ) return;
showProgress = nextLogTimeDurationUnits < (System.currentTimeMillis() + totalTime)/200;
if ( showProgress ) { //0 == rowsFetched % 1000 )
nextLogTimeDurationUnits++;
queryInfo.setText(
( 1 == repeatCount ? "" : "Repetition " + queryIndex + ". " ) +
"Fetching rows... " + (0==rowsFetched?"":rowsFetched));
}
rowsFetched++;
}
}
else {
ResultSetMetaData metadata = resultSet.getMetaData();
int cols = metadata.getColumnCount();
String[] columnNames = new String[cols];
for (int i = 0; i < cols; i++) {
columnNames[i] = metadata.getColumnName(i + 1);
}
resultsModel.setColumnIdentifiers(columnNames);
// Now add the cell "Editors" to the table
// We're not actually editing, but the cell editor
// is fired when the user double clicks. We will
// capture that event do useful stuff
for (int i = 0; i < cols; i++) {
results.getColumnModel().getColumn(i)
.setCellEditor(new QueryTabResultsEditorEvent(executingSQL));
}
// Adjust/Reset the cell renderer
if (Pattern.compile(WARNINGS_SQL).matcher(executingSQL.toLowerCase()).find()) {
results.setDefaultRenderer(Object.class, new ListWarningsTableCellRenderer());
} else {
results.setDefaultRenderer(Object.class, new ResetTableCellRenderer());
}
while (resultSet.next()) {
Object[] data = new Object[cols];
for (int i = 0; i < cols; i++)
data[i] = resultSet.getObject(i + 1); // getString(i + 1);
// Check now if the query was cancelled as data may be corrupted at this point
if ( startTime < lastCancelTime ) {
resultsModel.setColumnCount(0);
resultsModel.setRowCount(0);
return;
}
resultsModel.addRow(data);
showProgress = nextLogTimeDurationUnits < (System.currentTimeMillis() + totalTime)/200;
if ( showProgress ) { //0 == rowsFetched % 1000 )
nextLogTimeDurationUnits++;
queryInfo.setText(
( 1 == repeatCount ? "" : "Repetition " + queryIndex + ". " ) +
"Fetching rows... " + (0==rowsFetched?"":rowsFetched));
}
rowsFetched++;
}
}
resultSet.close();
}
aggregateRowCount += rowsFetched;
}
totalTime += System.currentTimeMillis();
// Release/Clear statement and result-set asap
if ( null != stmt ) { stmt.close(); stmt = null; }
int rowCount = results.getRowCount();
int columnCount = results.getColumnCount();
TableColumnModel columnModel = results.getColumnModel();
for (int y = 0; y < columnCount; y++) {
TableColumn column = columnModel.getColumn(y);
int maxWidth = column.getPreferredWidth();
for (int x = 0; x < rowCount; x++) {
maxWidth = Math.max(
maxWidth,
results.getCellRenderer(x, y)
.getTableCellRendererComponent(results, results.getValueAt(x, y), false, false, x, y)
.getPreferredSize().width);
// System.out.println(executingSQL);
// // Set the text blue, if we know it is a URL
// if ((executingSQL.equals(WARNINGS_SQL)) && (y == WARNINGS_COLUMN)) {
// results.getCellRenderer(x, WARNINGS_COLUMN)
// .getTableCellRendererComponent(results, results.getValueAt(x, WARNINGS_COLUMN), false, false, x, WARNINGS_COLUMN).setForeground(Color.BLUE);
// } else {
// results.getCellRenderer(x, y)
// .getTableCellRendererComponent(results, results.getValueAt(x, y), false, false, x, y).setForeground(Color.BLACK);
// }
}
column.setPreferredWidth(maxWidth + COLUMN_PADDING);
}
// input.setText("");
// currentSql = "";
queryInfo.setText(
"Completed at " + new SimpleDateFormat("HH:mm:ss").format(new Date()) + ". " +
( aggregateRowCount == rowCount ?
"Fetched " + rowCount + " rows." :
"Aggregate Fetch: " + aggregateRowCount + " rows. Last Fetch: " + rowCount + " rows."
) +
" Total Time: " + totalTime + "ms" +
" (Execution Time: " + execTime + "ms)" +
" "+(aggregateRowCount*1000/(0==totalTime ? 1:totalTime))+" rows/s"
);
setErrors();
} catch (Exception e) {
queryInfo.setText("Error/Warning at " + new SimpleDateFormat("HH:mm:ss").format(new Date()) + ". ");
// Search for root cause in case of IEX
Throwable cause = e;
String msg = "";
while (true) {
cause = cause.getCause();
if (cause != null) {
msg = cause.getMessage();
} else {
break;
}
}
String extractedCode = "";
if (msg.matches("^.*" + GaianTable.IEX_PREFIX.replaceAll("\\*","\\\\*") + ".*$")) {
extractedCode = msg.toString().split("'")[1].split(":")[0];
}
try {
if (null != GDBMessages.class.getDeclaredField(extractedCode) ) {
setErrorsWithCode(extractedCode, e);
} else {
throw new Exception();
}
} catch (Exception e1) {
setErrorsWithCode(GDBMessages.CLIENT_STMT_EXEC_RETURNED_ERROR, e);
}
} finally {
// Ensure statement and result-set are definitely released/cleared
if ( null != stmt ) { try { stmt.close(); } catch (SQLException e) {} stmt = null; }
if ( startTime > lastCancelTime ) {
executingSQL = null;
submit.setText("Run");
}
// Try to free up some memory if a large number of cells has been cleared.
long mem = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getCommitted();
long cellCount = (long) resultsModel.getRowCount() * resultsModel.getColumnCount();
if ( previousCellCount/10 > cellCount/9 || (previousCellCount/9 > cellCount/10 && memDecreasedSubstantially) ) {
int i=0;
while ( ++i < 100 && lastStartTime == startTime ) // only let one completing thread run this loop
try { System.gc(); Thread.sleep(100); } catch (InterruptedException e) {}
// System.out.println("Called GC " + i + " times");
}
if ( lastStartTime == startTime ) {
previousCellCount = cellCount;
memDecreasedSubstantially = mem - ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getCommitted() > mem/100;
}
}
}
}, "QueryExecutor").start();
}
private void back() {
sqlHistory.set(sqlHistoryIndex--, input.getText());
input.setText( sqlHistory.get(sqlHistoryIndex) );
if ( 0 == sqlHistoryIndex ) back.setEnabled(false);
forward.setEnabled(true);
}
private void forward() {
sqlHistory.set(sqlHistoryIndex++, input.getText());
input.setText( sqlHistory.get(sqlHistoryIndex) );
if ( sqlHistory.size()-1 == sqlHistoryIndex ) forward.setEnabled(false);
back.setEnabled(true);
}
// private void setErrors(Exception e) {
// setErrorsWithCode(null, e);
// }
private void setErrorsWithCode(String code, Exception e) {
List<String> errors = new LinkedList<String>();
StringBuffer extrace = new StringBuffer();
for ( StackTraceElement ste : e.getStackTrace() )
extrace.insert(0, ">" + ste.getMethodName() + ":" + ste.getLineNumber());
Throwable cause = e;
while (true) {
cause = cause.getCause();
if (null != cause) {
if (!errors.contains(cause.getMessage())) {
errors.add(cause.getMessage());
}
}
else {
break;
}
}
errors.add("Diag:"+extrace.toString());
setErrorsWithCode(code, errors.toArray(new String[errors.size()]));
}
private void setErrors(String... errors) {
setErrorsWithCode(null, errors);
}
private void setErrorsWithCode(String code, String... errors) {
errorsModel.clear();
if (errors.length == 0) {
if (null == conn) {
errorsModel.addElement("You must be connected to a GaianDB node in order to execute queries.");
}
else {
errorsModel.addElement("There are no errors or warnings to report.");
}
}
else {
for (String error : errors) {
if (null != code && errors[0].equals(error)) {
errorsModel.addElement(code + " : " + error);
} else {
errorsModel.addElement(error);
}
}
}
// Do we have a unique error code
try {
if ((null != code) && (null != GDBMessages.class.getDeclaredField(code)) && (Logger.findDocumentation(code).startsWith("file"))) {
lookupError.setEnabled(true);
} else {
lookupError.setEnabled(false);
}
} catch (Exception e) {
lookupError.setEnabled(false);
}
}
public void connected(Connection newConn) {
conn = newConn;
reconnectButton.setEnabled(true);
setErrors();
}
public void disconnected() {
if ( null != conn ) try { conn.close(); } catch ( SQLException e ) {}
conn = null;
setErrors();
}
public void activated() { }
public void deactivated() { }
public static void openErrorDoc(String errorCode) {
String code = null != errorCode ? errorCode : Logger.UNKNOWN_WARNING;
String doc = Logger.findDocumentation(code);
// Special chars
doc = doc.replaceAll(" ", "%20");
Desktop dt = Desktop.getDesktop();
try {
dt.browse(new URI(doc));
} catch (Exception e) {
// setErrorsWithCode(GDBMessages.CLIENT_DOC_LOOKUP_ERROR, "");
}
}
}
// This is called when a user double clicks on a table cell
class QueryTabResultsEditorEvent extends AbstractCellEditor implements TableCellEditor {
private static final long serialVersionUID = 2943781958170741556L;
// int rowIndex = -1;
// int colIndex = -1;
// String cellText = null;
String lastCall = null;
public QueryTabResultsEditorEvent(String sqlStatment) {
lastCall = sqlStatment;
}
// When editing begins...
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
// rowIndex = row;
// colIndex = column;
final String cellText = null==value ? "" : value.toString();
// Do what we do based on the last call
if (lastCall.toLowerCase().matches(QueryTab.WARNINGS_SQL)
&& (column == QueryTab.WARNINGS_COLUMN)) {
// Extract error code
String code = cellText.split(":")[0];
QueryTab.openErrorDoc(code);
}
return null;
}
// When editing finishes...
public Object getCellEditorValue() {
// Not needed
return null;
}
}
class ListWarningsTableCellRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1L;
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
Component c = super.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, column);
// Only for specific column
if (column == QueryTab.WARNINGS_COLUMN) {
c.setForeground(Color.BLUE);
} else {
c.setForeground(Color.BLACK);
}
return c;
}
}
class ResetTableCellRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1L;
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
Component c = super.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, column);
// For all cells
c.setForeground(Color.BLACK);
return c;
}
}