[#1177] Add SQL Console module to jOOQ - Code refactored to allow

evolutions (breakpoints, remote execution, filters, etc.)
This commit is contained in:
Chrriis 2012-04-29 23:10:51 +02:00
parent bb12708eb9
commit fbe09689d0
20 changed files with 1249 additions and 665 deletions

View File

@ -121,10 +121,21 @@ public class DebugListener extends DefaultExecuteListener {
return;
}
endExecutionTime = System.currentTimeMillis();
List<Debugger> sqlQueryDebuggerList = DebuggerRegistry.getDebuggerList();
if(sqlQueryDebuggerList.isEmpty()) {
List<Debugger> debuggerList = DebuggerRegistry.getDebuggerList();
if(debuggerList.isEmpty()) {
return;
}
boolean hasListener = false;
for(Debugger listener: debuggerList) {
LoggingListener loggingListener = listener.getLoggingListener();
if(loggingListener != null) {
hasListener = true;
break;
}
}
if(!hasListener) {
return;
}
ResultSet resultSet = ctx.resultSet();
String[] sql = ctx.batchSQL();
SqlQueryType sqlQueryType = SqlQueryType.detectType(sql[0]);
@ -135,22 +146,31 @@ public class DebugListener extends DefaultExecuteListener {
parameterDescription = ((UsageTrackingPreparedStatement) statement).getParameterDescription();
}
}
DebuggerData sqlQueryDebuggerData = new DebuggerData(sqlQueryType, sql, parameterDescription, startPreparationTime == 0? null: aggregatedPreparationDuration, startBindTime == 0? null: endBindTime - startBindTime, endExecutionTime - startExecutionTime);
for(Debugger listener: sqlQueryDebuggerList) {
listener.debugQueries(sqlQueryDebuggerData);
QueryLoggingData queryLoggingData = new QueryLoggingData(sqlQueryType, sql, parameterDescription, startPreparationTime == 0? null: aggregatedPreparationDuration, startBindTime == 0? null: endBindTime - startBindTime, endExecutionTime - startExecutionTime);
for(Debugger listener: debuggerList) {
LoggingListener loggingListener = listener.getLoggingListener();
if(loggingListener != null) {
loggingListener.logQueries(queryLoggingData);
}
}
if(resultSet != null) {
final int sqlQueryDebuggerDataID = sqlQueryDebuggerData.getID();
final int queryLoggingDataID = queryLoggingData.getID();
ResultSet newResultSet = new UsageTrackingResultSet(resultSet) {
@Override
protected void notifyData(long lifeTime, int readRows, int readCount, int writeCount) {
List<Debugger> sqlQueryDebuggerList = DebuggerRegistry.getDebuggerList();
if(sqlQueryDebuggerList.isEmpty()) {
List<Debugger> debuggerList = DebuggerRegistry.getDebuggerList();
if(debuggerList.isEmpty()) {
return;
}
DebuggerResultSetData sqlQueryDebuggerResultSetData = new DebuggerResultSetData(lifeTime, readRows, readCount, writeCount);
for(Debugger listener: sqlQueryDebuggerList) {
listener.debugResultSet(sqlQueryDebuggerDataID, sqlQueryDebuggerResultSetData);
ResultSetLoggingData resultSetLoggingData = null;
for(Debugger debugger: debuggerList) {
LoggingListener loggingListener = debugger.getLoggingListener();
if(loggingListener != null) {
if(resultSetLoggingData == null) {
resultSetLoggingData = new ResultSetLoggingData(lifeTime, readRows, readCount, writeCount);
}
loggingListener.logResultSet(queryLoggingDataID, resultSetLoggingData);
}
}
}
};

View File

@ -1,49 +1,57 @@
/**
* Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com
* Christopher Deckers, chrriis@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "jOOQ" 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 THE COPYRIGHT OWNER 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.jooq.debug;
/**
* @author Christopher Deckers
*/
public interface Debugger {
public void debugQueries(DebuggerData sqlQueryDebuggerData);
public void debugResultSet(int sqlQueryDebuggerDataID, DebuggerResultSetData sqlQueryDebuggerResultSetData);
}
/**
* Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com
* Christopher Deckers, chrriis@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "jOOQ" 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 THE COPYRIGHT OWNER 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.jooq.debug;
public interface Debugger {
public void setLoggingListener(LoggingListener loggingListener);
public LoggingListener getLoggingListener();
public boolean isExecutionSupported();
public StatementExecutor createStatementExecutor(String sql, int maxRSRowsParsing, int retainParsedRSDataRowCountThreshold);
public String[] getTableNames();
public String[] getTableColumnNames();
public boolean isReadOnly();
}

View File

@ -40,6 +40,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author Christopher Deckers
*/
@ -53,32 +54,12 @@ public class DebuggerRegistry {
public static void addSqlQueryDebugger(Debugger sqlQueryDebugger) {
synchronized(LOCK) {
debuggerList.add(sqlQueryDebugger);
for(DebuggerRegistryListener l: debuggerRegisterListenerList) {
l.notifyDebuggerListenersModified();
}
}
}
public static void removeSqlQueryDebugger(Debugger sqlQueryDebugger) {
synchronized(LOCK) {
debuggerList.remove(sqlQueryDebugger);
for(DebuggerRegistryListener l: debuggerRegisterListenerList) {
l.notifyDebuggerListenersModified();
}
}
}
private static final List<DebuggerRegistryListener> debuggerRegisterListenerList = new ArrayList<DebuggerRegistryListener>(1);
public static void addDebuggerRegisterListener(DebuggerRegistryListener listener) {
synchronized(LOCK) {
debuggerRegisterListenerList.add(listener);
}
}
public static void removeDebuggerRegisterListener(DebuggerRegistryListener listener) {
synchronized(LOCK) {
debuggerRegisterListenerList.remove(listener);
}
}

View File

@ -0,0 +1,113 @@
/**
* Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com
* Christopher Deckers, chrriis@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "jOOQ" 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 THE COPYRIGHT OWNER 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.jooq.debug;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.jooq.Field;
import org.jooq.Record;
import org.jooq.Table;
import org.jooq.debug.console.DatabaseDescriptor;
public class LocalDebugger implements Debugger {
private DatabaseDescriptor databaseDescriptor;
public LocalDebugger(DatabaseDescriptor databaseDescriptor) {
this.databaseDescriptor = databaseDescriptor;
}
private LoggingListener loggingListener;
@Override
public void setLoggingListener(LoggingListener loggingListener) {
this.loggingListener = loggingListener;
}
@Override
public LoggingListener getLoggingListener() {
return loggingListener;
}
@Override
public boolean isExecutionSupported() {
return databaseDescriptor != null;
}
@Override
public StatementExecutor createStatementExecutor(String sql, int maxRSRowsParsing, int retainParsedRSDataRowCountThreshold) {
return new StatementExecutorImpl(databaseDescriptor, sql, maxRSRowsParsing, retainParsedRSDataRowCountThreshold);
}
@Override
public String[] getTableNames() {
List<Table<?>> tableList = databaseDescriptor.getSchema().getTables();
List<String> tableNameList = new ArrayList<String>();
for(Table<? extends Record> table: tableList) {
String tableName = table.getName();
tableNameList.add(tableName);
}
Collections.sort(tableNameList, String.CASE_INSENSITIVE_ORDER);
return tableNameList.toArray(new String[0]);
}
@Override
public String[] getTableColumnNames() {
Set<String> columnNameSet = new HashSet<String>();
for(Table<?> table: databaseDescriptor.getSchema().getTables()) {
for(Field<?> field: table.getFields()) {
String columnName = field.getName();
columnNameSet.add(columnName);
}
}
String[] columnNames = columnNameSet.toArray(new String[0]);
Arrays.sort(columnNames, String.CASE_INSENSITIVE_ORDER);
return columnNames;
}
@Override
public boolean isReadOnly() {
return databaseDescriptor.isReadOnly();
}
}

View File

@ -0,0 +1,46 @@
/**
* Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com
* Christopher Deckers, chrriis@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "jOOQ" 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 THE COPYRIGHT OWNER 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.jooq.debug;
public interface LoggingListener {
public void logQueries(QueryLoggingData queryLoggingData);
public void logResultSet(int queryLoggingDataID, ResultSetLoggingData sqlQueryDebuggerResultSetData);
}

View File

@ -42,7 +42,7 @@ import java.io.Serializable;
/**
* @author Christopher Deckers
*/
public class DebuggerData implements Serializable {
public class QueryLoggingData implements Serializable {
private static volatile int nextID;
@ -57,7 +57,7 @@ public class DebuggerData implements Serializable {
private long threadID;
private StackTraceElement[] callerStackTraceElements;
public DebuggerData(SqlQueryType queryType, String[] queries, String parameterDescription, Long preparationDuration, Long bindingDuration, long executionDuration) {
public QueryLoggingData(SqlQueryType queryType, String[] queries, String parameterDescription, Long preparationDuration, Long bindingDuration, long executionDuration) {
this.id = nextID++;
Thread currentThread = Thread.currentThread();
this.threadName = currentThread.getName();

View File

@ -41,7 +41,7 @@ import java.io.Serializable;
/**
* @author Christopher Deckers
*/
public class DebuggerResultSetData implements Serializable {
public class ResultSetLoggingData implements Serializable {
private static volatile int nextID;
@ -51,7 +51,7 @@ public class DebuggerResultSetData implements Serializable {
private final int readCount;
private final int writeCount;
public DebuggerResultSetData(long lifeTime, final int readRows, final int readCount, final int writeCount) {
public ResultSetLoggingData(long lifeTime, final int readRows, final int readCount, final int writeCount) {
this.id = nextID++;
this.lifeTime = lifeTime;
this.readRows = readRows;

View File

@ -0,0 +1,60 @@
/**
* Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com
* Christopher Deckers, chrriis@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "jOOQ" 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 THE COPYRIGHT OWNER 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.jooq.debug;
public class StatementExecution {
private long executionDuration;
private StatementExecutionResult[] results;
public StatementExecution(long executionDuration, StatementExecutionResult... results) {
this.executionDuration = executionDuration;
this.results = results;
}
public StatementExecutionResult[] getResults() {
return results;
}
public long getExecutionDuration() {
return executionDuration;
}
}

View File

@ -0,0 +1,67 @@
/**
* Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com
* Christopher Deckers, chrriis@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "jOOQ" 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 THE COPYRIGHT OWNER 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.jooq.debug;
import java.io.PrintWriter;
import java.io.StringWriter;
public class StatementExecutionMessageResult implements StatementExecutionResult {
private String message;
private boolean isError;
public StatementExecutionMessageResult(String message, boolean isError) {
this.message = message;
this.isError = isError;
}
public StatementExecutionMessageResult(Exception e) {
StringWriter stringWriter = new StringWriter();
e.printStackTrace(new PrintWriter(stringWriter));
this.message = stringWriter.toString();
isError = true;
}
public String getMessage() {
return message;
}
public boolean isError() {
return isError;
}
}

View File

@ -1,43 +1,42 @@
/**
* Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com
* Christopher Deckers, chrriis@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "jOOQ" 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 THE COPYRIGHT OWNER 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.jooq.debug;
public interface DebuggerRegistryListener {
public void notifyDebuggerListenersModified();
}
/**
* Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com
* Christopher Deckers, chrriis@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "jOOQ" 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 THE COPYRIGHT OWNER 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.jooq.debug;
public interface StatementExecutionResult {
}

View File

@ -0,0 +1,177 @@
/**
* Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com
* Christopher Deckers, chrriis@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "jOOQ" 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 THE COPYRIGHT OWNER 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.jooq.debug;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import org.jooq.debug.console.misc.Utils;
public class StatementExecutionResultSetResult implements StatementExecutionResult {
public static class TypeInfo {
private String columnName;
private int precision;
private int scale;
private int nullable = ResultSetMetaData.columnNullableUnknown;
TypeInfo(ResultSetMetaData metaData, int column) {
try {
columnName = metaData.getColumnTypeName(column + 1);
precision = metaData.getPrecision(column + 1);
scale = metaData.getScale(column + 1);
nullable = metaData.isNullable(column + 1);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(columnName);
if(precision != 0) {
sb.append(" (" + precision + (scale != 0? ", " + scale: "") + ")");
}
if(nullable != ResultSetMetaData.columnNullableUnknown) {
sb.append(nullable == ResultSetMetaData.columnNoNulls? " not null": " null");
}
return sb.toString();
}
}
private ResultSet rs;
private String[] columnNames;
private TypeInfo[] typeInfos;
private Class<?>[] columnClasses;
private Object[][] rowData;
private int rowCount;
private long resultSetParsingDuration;
private int retainParsedRSDataRowCountThreshold;
private boolean isReadOnly;
public StatementExecutionResultSetResult(ResultSet rs, String[] columnNames, TypeInfo[] typeInfos, Class<?>[] columnClasses, Object[][] rowData, int rowCount, long resultSetParsingDuration, int retainParsedRSDataRowCountThreshold, boolean isReadOnly) {
this.rs = rs;
this.columnNames = columnNames;
this.typeInfos = typeInfos;
this.columnClasses = columnClasses;
this.rowData = rowData;
this.rowCount = rowCount;
this.resultSetParsingDuration = resultSetParsingDuration;
this.retainParsedRSDataRowCountThreshold = retainParsedRSDataRowCountThreshold;
this.isReadOnly = isReadOnly;
}
public String[] getColumnNames() {
return columnNames;
}
public TypeInfo[] getTypeInfos() {
return typeInfos;
}
public Class<?>[] getColumnClasses() {
return columnClasses;
}
/**
* @return the data or an empty array of arrays if <code>rowCount</code> is over <code>retainParsedRSDataRowCountThreshold</code>.
*/
public Object[][] getRowData() {
return rowData;
}
public int getRowCount() {
return rowCount;
}
public long getResultSetParsingDuration() {
return resultSetParsingDuration;
}
public int getRetainParsedRSDataRowCountThreshold() {
return retainParsedRSDataRowCountThreshold;
}
public boolean deleteRow(int row) {
try {
rs.absolute(row);
rs.deleteRow();
Object[][] newRowData = new Object[rowData.length - 1][];
System.arraycopy(rowData, 0, newRowData, 0, row);
System.arraycopy(rowData, row + 1, newRowData, row, newRowData.length - row - 1);
rowData = newRowData;
return true;
} catch (SQLException ex) {
ex.printStackTrace();
}
return false;
}
public boolean setValueAt(Object o, int row, int col) {
if(Utils.equals(o, rowData[row][col])) {
return false;
}
try {
rs.absolute(row + 1);
rs.updateObject(col + 1, o);
rs.updateRow();
rowData[row][col] = o;
return true;
} catch (SQLException e) {
e.printStackTrace();
try {
rs.cancelRowUpdates();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
return false;
}
public boolean isEditable() {
try {
return rs.getConcurrency() == ResultSet.CONCUR_UPDATABLE && !isReadOnly;
} catch (SQLException e) {
}
return false;
}
}

View File

@ -0,0 +1,46 @@
/**
* Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com
* Christopher Deckers, chrriis@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "jOOQ" 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 THE COPYRIGHT OWNER 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.jooq.debug;
public interface StatementExecutor {
public StatementExecution execute();
public void stopExecution();
}

View File

@ -0,0 +1,301 @@
/**
* Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com
* Christopher Deckers, chrriis@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "jOOQ" 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 THE COPYRIGHT OWNER 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.jooq.debug;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jooq.SQLDialect;
import org.jooq.debug.StatementExecutionResultSetResult.TypeInfo;
import org.jooq.debug.console.DatabaseDescriptor;
import org.jooq.debug.console.misc.Utils;
public class StatementExecutorImpl implements StatementExecutor {
private DatabaseDescriptor databaseDescriptor;
private String sql;
private int maxRSRowsParsing;
private int retainParsedRSDataRowCountThreshold;
public StatementExecutorImpl(DatabaseDescriptor databaseDescriptor, String sql, int maxRSRowsParsing, int retainParsedRSDataRowCountThreshold) {
this.databaseDescriptor = databaseDescriptor;
this.sql = sql;
this.maxRSRowsParsing = maxRSRowsParsing;
this.retainParsedRSDataRowCountThreshold = retainParsedRSDataRowCountThreshold;
}
private volatile Connection conn;
private volatile Statement stmt;
private volatile Thread evaluationThread;
@Override
public StatementExecution execute() {
boolean isAllowed = true;
if(databaseDescriptor.isReadOnly()) {
String simplifiedSql = sql.replaceAll("'[^']*'", "");
Matcher matcher = Pattern.compile("[a-zA-Z_0-9\\$]+").matcher(simplifiedSql);
boolean isFirst = true;
while(matcher.find()) {
String word = simplifiedSql.substring(matcher.start(), matcher.end()).toUpperCase(Locale.ENGLISH);
if(isFirst && !word.equals("SELECT")) {
isAllowed = false;
break;
}
isFirst = false;
for(String keyword: new String[] {
"INSERT",
"UPDATE",
"DELETE",
"ALTER",
"DROP",
"CREATE",
"EXEC",
"EXECUTE",
}) {
if(word.equals(keyword)) {
isAllowed = false;
break;
}
}
}
}
if(!isAllowed) {
return new StatementExecution(0, new StatementExecutionMessageResult("The database is not editable but the statement to evaluate is a modification statement!", true));
}
closeConnection();
evaluationThread = Thread.currentThread();
long start = System.currentTimeMillis();
try {
conn = databaseDescriptor.createConnection();
stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
// If no error, adjust start to begining of actual execution.
start = System.currentTimeMillis();
if(evaluationThread != Thread.currentThread()) {
long executionDuration = System.currentTimeMillis() - start;
return new StatementExecution(executionDuration, new StatementExecutionMessageResult("Interrupted by user after " + Utils.formatDuration(executionDuration), true));
}
boolean executeResult;
try {
executeResult = stmt.execute(sql);
} catch(SQLException e) {
long executionDuration = System.currentTimeMillis() - start;
if(evaluationThread != Thread.currentThread()) {
return new StatementExecution(executionDuration, new StatementExecutionMessageResult("Interrupted by user after " + Utils.formatDuration(executionDuration), true));
}
return new StatementExecution(executionDuration, new StatementExecutionMessageResult(e));
}
final long executionDuration = System.currentTimeMillis() - start;
if(evaluationThread != Thread.currentThread()) {
return new StatementExecution(executionDuration, new StatementExecutionMessageResult("Interrupted by user after " + Utils.formatDuration(executionDuration), true));
}
List<StatementExecutionResult> statementExecutionResultList = new ArrayList<StatementExecutionResult>();
do {
StatementExecutionResult statementExecutionResult;
if(executeResult) {
final ResultSet rs = stmt.getResultSet();
ResultSetMetaData metaData = rs.getMetaData();
final String[] columnNames = new String[metaData.getColumnCount()];
final int[] columnTypes = new int[columnNames.length];
final TypeInfo[] typeInfos = new TypeInfo[columnNames.length];
final Class<?>[] columnClasses = new Class[columnNames.length];
for(int i=0; i<columnNames.length; i++) {
columnNames[i] = metaData.getColumnName(i + 1);
if(columnNames[i] == null || columnNames[i].length() == 0) {
columnNames[i] = " ";
}
typeInfos[i] = new TypeInfo(metaData, i);
int type = metaData.getColumnType(i + 1);
columnTypes[i] = type;
switch(type) {
case Types.CLOB:
columnClasses[i] = String.class;
break;
case Types.BLOB:
columnClasses[i] = byte[].class;
break;
default:
String columnClassName = metaData.getColumnClassName(i + 1);
if(columnClassName == null) {
System.err.println("Unknown SQL Type for \"" + columnNames[i] + "\" in " + getClass().getSimpleName() + ": " + metaData.getColumnTypeName(i));
columnClasses[i] = Object.class;
} else {
columnClasses[i] = Class.forName(columnClassName);
}
break;
}
}
if(evaluationThread != Thread.currentThread()) {
return new StatementExecution(executionDuration, new StatementExecutionMessageResult("Interrupted by user after " + Utils.formatDuration(executionDuration), true));
}
final List<Object[]> rowDataList = new ArrayList<Object[]>();
int rowCount = 0;
long rsStart = System.currentTimeMillis();
while(rs.next() && rowCount < maxRSRowsParsing) {
if(evaluationThread != Thread.currentThread()) {
return new StatementExecution(executionDuration, new StatementExecutionMessageResult("Interrupted by user after " + Utils.formatDuration(executionDuration), true));
}
rowCount++;
Object[] rowData = new Object[columnNames.length];
for(int i=0; i<columnNames.length; i++) {
switch(columnTypes[i]) {
case Types.CLOB: {
Clob clob = rs.getClob(i + 1);
if(clob != null) {
StringWriter stringWriter = new StringWriter();
char[] chars = new char[1024];
Reader reader = new BufferedReader(clob.getCharacterStream());
for(int count; (count=reader.read(chars))>=0; ) {
stringWriter.write(chars, 0, count);
}
rowData[i] = stringWriter.toString();
} else {
rowData[i] = null;
}
break;
}
case Types.BLOB: {
Blob blob = rs.getBlob(i + 1);
if(blob != null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
InputStream in = new BufferedInputStream(blob.getBinaryStream());
for(int count; (count=in.read(bytes))>=0; ) {
baos.write(bytes, 0, count);
}
rowData[i] = baos.toByteArray();
} else {
rowData[i] = null;
}
break;
}
default:
Object object = rs.getObject(i + 1);
if(object != null) {
String className = object.getClass().getName();
if ("oracle.sql.TIMESTAMP".equals(className) || "oracle.sql.TIMESTAMPTZ".equals(className)) {
object = rs.getTimestamp(i + 1);
}
// Probably something to do for oracle.sql.DATE
}
rowData[i] = object;
break;
}
}
if(rowCount <= retainParsedRSDataRowCountThreshold) {
rowDataList.add(rowData);
} else if(rowCount == retainParsedRSDataRowCountThreshold + 1) {
rowDataList.clear();
}
}
final long resultSetParsingDuration = System.currentTimeMillis() - rsStart;
statementExecutionResult = new StatementExecutionResultSetResult(rs, columnNames, typeInfos, columnClasses, rowDataList.toArray(new Object[0][]), rowCount, resultSetParsingDuration, retainParsedRSDataRowCountThreshold, databaseDescriptor.isReadOnly());
} else {
final int updateCount = stmt.getUpdateCount();
statementExecutionResult = new StatementExecutionMessageResult(Utils.formatDuration(executionDuration) + "> " + updateCount + " row(s) affected.", false);
}
if(databaseDescriptor.getSQLDialect() == SQLDialect.SQLSERVER) {
try {
executeResult = stmt.getMoreResults(Statement.KEEP_CURRENT_RESULT);
} catch(Exception e) {
executeResult = stmt.getMoreResults();
}
} else {
executeResult = false;
}
statementExecutionResultList.add(statementExecutionResult);
} while(executeResult || stmt.getUpdateCount() != -1);
return new StatementExecution(executionDuration, statementExecutionResultList.toArray(new StatementExecutionResult[0]));
} catch(Exception e) {
long executionDuration = System.currentTimeMillis() - start;
return new StatementExecution(executionDuration, new StatementExecutionMessageResult(e));
} finally {
if(databaseDescriptor.isReadOnly()) {
closeConnection();
}
}
// TODO: implement
}
private void closeConnection() {
if (conn != null) {
if (stmt != null) {
try {
stmt.cancel();
} catch (Exception e) {
}
try {
stmt.close();
} catch (Exception e) {
}
}
stmt = null;
try {
conn.close();
} catch (Exception e) {
}
conn = null;
}
}
@Override
public void stopExecution() {
if(evaluationThread != null) {
evaluationThread = null;
closeConnection();
}
}
}

View File

@ -59,7 +59,6 @@ import java.awt.font.TextAttribute;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@ -89,8 +88,9 @@ import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.jooq.Record;
import org.jooq.Table;
import org.jooq.debug.Debugger;
import org.jooq.debug.DebuggerRegistry;
import org.jooq.debug.LocalDebugger;
import org.jooq.debug.console.remote.RemoteDebuggerClient;
/**
@ -99,11 +99,13 @@ import org.jooq.debug.console.remote.RemoteDebuggerClient;
@SuppressWarnings("serial")
public class Console extends JFrame {
private Debugger debugger;
private JTabbedPane mainTabbedPane;
private JTabbedPane editorTabbedPane;
public Console(final DatabaseDescriptor editorDatabaseDescriptor, boolean isShowingLoggingTab) {
public Console(final Debugger debugger, boolean isShowingLoggingTab) {
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
this.debugger = debugger;
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
fileMenu.setMnemonic('F');
@ -193,15 +195,15 @@ public class Console extends JFrame {
setJMenuBar(menuBar);
mainTabbedPane = new JTabbedPane();
String title = "jOOQ Console";
if(editorDatabaseDescriptor != null) {
String schemaName = editorDatabaseDescriptor.getSchema().getName();
if(schemaName != null && schemaName.length() != 0) {
title += " - " + schemaName;
}
}
// if(editorDatabaseDescriptor != null) {
// String schemaName = editorDatabaseDescriptor.getSchema().getName();
// if(schemaName != null && schemaName.length() != 0) {
// title += " - " + schemaName;
// }
// }
setTitle(title);
if(editorDatabaseDescriptor != null) {
addEditorTab(editorDatabaseDescriptor);
if(debugger.isExecutionSupported()) {
addEditorTab();
}
if(isShowingLoggingTab) {
addLoggerTab();
@ -210,7 +212,7 @@ public class Console extends JFrame {
setLocationByPlatform(true);
setSize(800, 600);
addNotify();
if(editorDatabaseDescriptor != null) {
if(debugger.isExecutionSupported()) {
getFocusedEditorPane().adjustDefaultFocus();
}
addWindowListener(new WindowAdapter() {
@ -231,7 +233,7 @@ public class Console extends JFrame {
}
if(editorTabbedPane != null) {
for(int i=editorTabbedPane.getTabCount()-2; i>=0; i--) {
((EditorPane)editorTabbedPane.getComponentAt(i)).closeConnection();
((EditorPane)editorTabbedPane.getComponentAt(i)).closeLastExecution();
}
}
}
@ -239,13 +241,13 @@ public class Console extends JFrame {
private LoggerPane sqlLoggerPane;
private void addLoggerTab() {
sqlLoggerPane = new LoggerPane();
sqlLoggerPane = new LoggerPane(debugger);
mainTabbedPane.addTab("Logger", sqlLoggerPane);
}
private void addEditorTab(final DatabaseDescriptor databaseDescriptor) {
private void addEditorTab() {
JPanel editorsPane = new JPanel(new BorderLayout());
final String[] tableNames = getTableNames(databaseDescriptor);
final String[] tableNames = debugger.getTableNames();
final JList tableNamesJList = new JList(tableNames);
tableNamesJList.addMouseListener(new MouseAdapter() {
@Override
@ -265,7 +267,7 @@ public class Console extends JFrame {
@Override
public void stateChanged(ChangeEvent e) {
if(!isAdjusting && editorTabbedPane.getSelectedIndex() == editorTabbedPane.getTabCount() - 1) {
addSQLEditorPane(databaseDescriptor);
addSQLEditorPane();
}
}
});
@ -374,14 +376,21 @@ public class Console extends JFrame {
tableNamePane.add(tableNameFilterTextField, BorderLayout.NORTH);
tableNamePane.add(new JScrollPane(tableNamesJList), BorderLayout.CENTER);
JSplitPane horizontalSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, tableNamePane, editorTabbedPane);
addSQLEditorPane(databaseDescriptor);
addSQLEditorPane();
editorsPane.add(horizontalSplitPane, BorderLayout.CENTER);
mainTabbedPane.addTab("Editor", editorsPane);
}
public static void openConsole(DatabaseDescriptor databaseDescriptor, boolean isLoggingActive) {
Console sqlConsoleFrame = new Console(databaseDescriptor, true);
final LocalDebugger debugger = new LocalDebugger(databaseDescriptor);
Console sqlConsoleFrame = new Console(debugger, true);
sqlConsoleFrame.setLoggingActive(isLoggingActive);
sqlConsoleFrame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
DebuggerRegistry.removeSqlQueryDebugger(debugger);
}
});
sqlConsoleFrame.setVisible(true);
}
@ -396,10 +405,10 @@ public class Console extends JFrame {
private boolean isAdjusting;
private int contextCount = 1;
private void addSQLEditorPane(DatabaseDescriptor databaseDescriptor) {
private void addSQLEditorPane() {
isAdjusting = true;
int index = editorTabbedPane.getTabCount() - 1;
final EditorPane sqlEditorPane = new EditorPane(databaseDescriptor);
final EditorPane sqlEditorPane = new EditorPane(debugger);
String title = "Context " + contextCount++;
editorTabbedPane.insertTab(title, null, sqlEditorPane, null, index);
final JPanel tabComponent = new JPanel(new BorderLayout());
@ -417,7 +426,7 @@ public class Console extends JFrame {
if(editorTabbedPane.getTabCount() > 2) {
for(int i=editorTabbedPane.getTabCount()-1; i>=0; i--) {
if(editorTabbedPane.getTabComponentAt(i) == tabComponent) {
((EditorPane)editorTabbedPane.getComponentAt(i)).closeConnection();
((EditorPane)editorTabbedPane.getComponentAt(i)).closeLastExecution();
editorTabbedPane.removeTabAt(i);
if(i == editorTabbedPane.getTabCount() - 1) {
editorTabbedPane.setSelectedIndex(i - 1);
@ -458,25 +467,15 @@ public class Console extends JFrame {
return (EditorPane)editorTabbedPane.getSelectedComponent();
}
private static String[] getTableNames(DatabaseDescriptor databaseDescriptor) {
List<Table<?>> tableList = databaseDescriptor.getSchema().getTables();
List<String> tableNameList = new ArrayList<String>();
for(Table<? extends Record> table: tableList) {
String tableName = table.getName();
tableNameList.add(tableName);
}
Collections.sort(tableNameList, String.CASE_INSENSITIVE_ORDER);
return tableNameList.toArray(new String[0]);
}
public static void main(String[] args) {
if(args.length < 2) {
System.out.println("Please specify IP and port of a running RemoteDebuggerServer");
System.out.println("Usage: Console <ip> <port>");
return;
}
final Debugger debugger;
try {
new RemoteDebuggerClient(args[0], Integer.parseInt(args[1]));
debugger = new RemoteDebuggerClient(args[0], Integer.parseInt(args[1]));
} catch(Exception e) {
e.printStackTrace();
return;
@ -489,7 +488,7 @@ public class Console extends JFrame {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
Console sqlConsoleFrame = new Console(null, true);
Console sqlConsoleFrame = new Console(debugger, true);
sqlConsoleFrame.setDefaultCloseOperation(EXIT_ON_CLOSE);
sqlConsoleFrame.setVisible(true);
}

View File

@ -53,32 +53,15 @@ import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -121,10 +104,12 @@ import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableRowSorter;
import javax.swing.text.BadLocationException;
import org.jooq.Field;
import org.jooq.Record;
import org.jooq.SQLDialect;
import org.jooq.Table;
import org.jooq.debug.Debugger;
import org.jooq.debug.StatementExecution;
import org.jooq.debug.StatementExecutionMessageResult;
import org.jooq.debug.StatementExecutionResult;
import org.jooq.debug.StatementExecutionResultSetResult;
import org.jooq.debug.StatementExecutor;
import org.jooq.debug.console.misc.JTableX;
import org.jooq.debug.console.misc.Utils;
@ -140,17 +125,18 @@ public class EditorPane extends JPanel {
private boolean isUsingMaxRowCount = true;
private JFormattedTextField displayedRowCountField;
private DatabaseDescriptor databaseDescriptor;
private SqlTextArea editorTextArea;
private JPanel southPanel = new JPanel(new BorderLayout());
private boolean isDBEditable;
private Debugger debugger;
private StatementExecutor lastStatementExecutor;
private JButton startButton;
private JButton stopButton;
EditorPane(DatabaseDescriptor databaseDescriptor) {
EditorPane(Debugger debugger) {
super(new BorderLayout());
this.databaseDescriptor = databaseDescriptor;
this.isDBEditable = !databaseDescriptor.isReadOnly();
this.debugger = debugger;
this.isDBEditable = !debugger.isReadOnly();
setOpaque(false);
JPanel northPanel = new JPanel(new BorderLayout());
northPanel.setOpaque(false);
@ -177,8 +163,7 @@ public class EditorPane extends JPanel {
stopButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
evaluationThread = null;
closeConnection();
closeLastExecution();
}
});
northWestPanel.add(stopButton);
@ -225,10 +210,7 @@ public class EditorPane extends JPanel {
new Thread("SQLConsole - Interruption") {
@Override
public void run() {
if(evaluationThread != null) {
evaluationThread = null;
closeConnection();
}
closeLastExecution();
}
}.start();
break;
@ -312,58 +294,19 @@ public class EditorPane extends JPanel {
southPanel.repaint();
}
private static class TypeInfo {
private String columnName;
private int precision;
private int scale;
private int nullable = ResultSetMetaData.columnNullableUnknown;
TypeInfo(ResultSetMetaData metaData, int column) {
try {
columnName = metaData.getColumnTypeName(column);
precision = metaData.getPrecision(column);
scale = metaData.getScale(column);
nullable = metaData.isNullable(column);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(columnName);
if(precision != 0) {
sb.append(" (" + precision + (scale != 0? ", " + scale: "") + ")");
}
if(nullable != ResultSetMetaData.columnNullableUnknown) {
sb.append(nullable == ResultSetMetaData.columnNoNulls? " not null": " null");
}
return sb.toString();
}
}
private volatile Connection conn;
private volatile Statement stmt;
private volatile Thread evaluationThread;
private void evaluate_unrestricted(final String sql) {
final int maxDisplayedRowCount = ((Number)displayedRowCountField.getValue()).intValue();
evaluationThread = new Thread("SQLConsole - Evaluation") {
Thread evaluationThread = new Thread("SQLConsole - Evaluation") {
@Override
public void run() {
evaluate_unrestricted_nothread(sql, maxDisplayedRowCount);
evaluationThread = null;
}
};
evaluationThread.start();
}
private void evaluate_unrestricted_nothread(final String sql, final int maxDisplayedRowCount) {
closeConnection();
closeLastExecution();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
@ -372,194 +315,14 @@ public class EditorPane extends JPanel {
stopButton.setToolTipText("Query started on " + Utils.formatDateTimeTZ(new Date()));
}
});
StatementExecutor statementExecutor;
synchronized (debugger) {
statementExecutor = debugger.createStatementExecutor(sql, isUsingMaxRowCount? MAX_ROW_COUNT: Integer.MAX_VALUE, maxDisplayedRowCount);
lastStatementExecutor = statementExecutor;
}
StatementExecution statementExecution;
try {
conn = databaseDescriptor.createConnection();
stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
final long start = System.currentTimeMillis();
if(evaluationThread != Thread.currentThread()) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
setMessage(addResultPane(), "Interrupted by user after " + Utils.formatDuration(System.currentTimeMillis() - start), true);
}
});
return;
}
boolean executeResult;
try {
executeResult = stmt.execute(sql);
} catch(SQLException e) {
if(evaluationThread != Thread.currentThread()) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
setMessage(addResultPane(), "Interrupted by user after " + Utils.formatDuration(System.currentTimeMillis() - start), true);
}
});
return;
}
throw e;
}
final long duration = System.currentTimeMillis() - start;
if(evaluationThread != Thread.currentThread()) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
setMessage(addResultPane(), "Interrupted by user after " + Utils.formatDuration(duration), true);
}
});
return;
}
do {
if(executeResult) {
final ResultSet rs = stmt.getResultSet();
ResultSetMetaData metaData = rs.getMetaData();
// The first column is the line count
final String[] columnNames = new String[metaData.getColumnCount() + 1];
final int[] columnTypes = new int[columnNames.length];
final TypeInfo[] typeInfos = new TypeInfo[columnNames.length];
final Class<?>[] columnClasses = new Class[columnNames.length];
columnNames[0] = "";
columnClasses[0] = Integer.class;
for(int i=1; i<columnNames.length; i++) {
columnNames[i] = metaData.getColumnName(i);
if(columnNames[i] == null || columnNames[i].length() == 0) {
columnNames[i] = " ";
}
typeInfos[i] = new TypeInfo(metaData, i);
int type = metaData.getColumnType(i);
columnTypes[i] = type;
switch(type) {
case Types.CLOB:
columnClasses[i] = String.class;
break;
case Types.BLOB:
columnClasses[i] = byte[].class;
break;
default:
String columnClassName = metaData.getColumnClassName(i);
if(columnClassName == null) {
System.err.println("Unknown SQL Type for \"" + columnNames[i] + "\" in SQLEditorPane: " + metaData.getColumnTypeName(i));
columnClasses[i] = Object.class;
} else {
columnClasses[i] = Class.forName(columnClassName);
}
break;
}
}
if(evaluationThread != Thread.currentThread()) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
setMessage(addResultPane(), "Interrupted by user after " + Utils.formatDuration(duration), true);
}
});
return;
}
final List<Object[]> rowDataList = new ArrayList<Object[]>();
int rowCount = 0;
long rsStart = System.currentTimeMillis();
while(rs.next() && (!isUsingMaxRowCount || rowCount < MAX_ROW_COUNT)) {
if(evaluationThread != Thread.currentThread()) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
setMessage(addResultPane(), "Interrupted by user after " + Utils.formatDuration(duration), true);
}
});
return;
}
rowCount++;
Object[] rowData = new Object[columnNames.length];
rowData[0] = rowCount;
for(int i=1; i<columnNames.length; i++) {
switch(columnTypes[i]) {
case Types.CLOB: {
Clob clob = rs.getClob(i);
if(clob != null) {
StringWriter stringWriter = new StringWriter();
char[] chars = new char[1024];
Reader reader = new BufferedReader(clob.getCharacterStream());
for(int count; (count=reader.read(chars))>=0; ) {
stringWriter.write(chars, 0, count);
}
rowData[i] = stringWriter.toString();
} else {
rowData[i] = null;
}
break;
}
case Types.BLOB: {
Blob blob = rs.getBlob(i);
if(blob != null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
InputStream in = new BufferedInputStream(blob.getBinaryStream());
for(int count; (count=in.read(bytes))>=0; ) {
baos.write(bytes, 0, count);
}
rowData[i] = baos.toByteArray();
} else {
rowData[i] = null;
}
break;
}
default:
Object object = rs.getObject(i);
if(object != null) {
String className = object.getClass().getName();
if ("oracle.sql.TIMESTAMP".equals(className) || "oracle.sql.TIMESTAMPTZ".equals(className)) {
object = rs.getTimestamp(i);
}
// Probably something to do for oracle.sql.DATE
}
rowData[i] = object;
break;
}
}
if(rowCount <= maxDisplayedRowCount) {
rowDataList.add(rowData);
} else if(rowCount == maxDisplayedRowCount + 1) {
rowDataList.clear();
}
}
final long rsDuration = System.currentTimeMillis() - rsStart;
final int rowCount_ = rowCount;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
addResultTable(sql, duration, rs, columnNames, typeInfos, columnClasses, rowDataList, rowCount_, rsDuration, maxDisplayedRowCount);
}
});
} else {
final int updateCount = stmt.getUpdateCount();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
setMessage(addResultPane(), Utils.formatDuration(duration) + "> " + updateCount + " row(s) affected.", false);
}
});
}
if(databaseDescriptor.getSQLDialect() == SQLDialect.SQLSERVER) {
try {
executeResult = stmt.getMoreResults(Statement.KEEP_CURRENT_RESULT);
} catch(Exception e) {
executeResult = stmt.getMoreResults();
}
} else {
executeResult = false;
}
} while(executeResult || stmt.getUpdateCount() != -1);
} catch(Exception e) {
StringWriter stringWriter = new StringWriter();
e.printStackTrace(new PrintWriter(stringWriter));
final String message = stringWriter.toString();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
setMessage(addResultPane(), message, true);
}
});
statementExecution = statementExecutor.execute();
} finally {
SwingUtilities.invokeLater(new Runnable() {
@Override
@ -569,86 +332,40 @@ public class EditorPane extends JPanel {
stopButton.setToolTipText(null);
}
});
if(!isDBEditable) {
closeConnection();
}
}
final StatementExecutionResult[] results = statementExecution.getResults();
final long executionDuration = statementExecution.getExecutionDuration();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
for(StatementExecutionResult result: results) {
if(result instanceof StatementExecutionMessageResult) {
StatementExecutionMessageResult messageResult = (StatementExecutionMessageResult)result;
setMessage(addResultPane(), messageResult.getMessage(), messageResult.isError());
} else if(result instanceof StatementExecutionResultSetResult) {
addResultTable(sql, executionDuration, (StatementExecutionResultSetResult)result);
} else {
throw new IllegalStateException("Unknown result class: " + result.getClass().getName());
}
}
}
});
}
private void addResultTable(final String sql, long duration, final ResultSet rs, final String[] columnNames, final TypeInfo[] typeInfos, final Class<?>[] columnClasses, final List<Object[]> rowDataList, int rowCount, long rsDuration, int maxDisplayedRowCount) {
private void addResultTable(final String sql, long duration, final StatementExecutionResultSetResult resultSetResult) {
int rowCount = resultSetResult.getRowCount();
JPanel resultPane = addResultPane();
final JLabel label = new JLabel(" " + rowCount + " rows");
FlowLayout flowLayout = new FlowLayout(FlowLayout.LEFT, 0, 0);
flowLayout.setAlignOnBaseline(true);
JPanel statusCountPane = new JPanel(flowLayout);
if(rowCount <= maxDisplayedRowCount) {
final JTableX table = new JTableX(new AbstractTableModel() {
@Override
public String getColumnName(int column) {
return columnNames[column].toString();
}
@Override
public int getRowCount() {
return rowDataList.size();
}
@Override
public int getColumnCount() {
return columnNames.length;
}
@Override
public Object getValueAt(int row, int col) {
return rowDataList.get(row)[col];
}
@Override
public void setValueAt(Object o, int row, int col) {
if(Utils.equals(o, rowDataList.get(row)[col])) {
return;
}
int dbRow = (Integer)rowDataList.get(row)[0];
try {
rs.absolute(dbRow);
rs.updateObject(col, o);
rs.updateRow();
rowDataList.get(row)[col] = o;
} catch (SQLException e) {
e.printStackTrace();
try {
rs.cancelRowUpdates();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
try {
if(rs.getConcurrency() == ResultSet.CONCUR_UPDATABLE) {
return isDBEditable && columnIndex > 0;
}
} catch (SQLException e) {
}
return false;
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return columnClasses[columnIndex];
}
}) {
@Override
public TableCellEditor getCellEditor(int row, int column) {
TableCellEditor editor = super.getCellEditor(row, column);
if(editor instanceof DefaultCellEditor) {
DefaultCellEditor defaultEditor = (DefaultCellEditor) editor;
defaultEditor.setClickCountToStart(2);
}
return editor;
}
};
if(rowCount <= resultSetResult.getRetainParsedRSDataRowCountThreshold()) {
final JTableX table = new ResultTable(resultSetResult);
JTableHeader tableHeader = new JTableHeader(table.getColumnModel()) {
@Override
public String getToolTipText(MouseEvent e) {
int col = getTable().convertColumnIndexToModel(columnAtPoint(e.getPoint()));
return col == 0? null: typeInfos[col].toString();
return col == 0? null: resultSetResult.getTypeInfos()[col - 1].toString();
}
};
ToolTipManager.sharedInstance().registerComponent(tableHeader);
@ -716,7 +433,7 @@ public class EditorPane extends JPanel {
@Override
public void valueChanged(ListSelectionEvent e) {
int selectedRowCount = table.getSelectedRowCount();
label.setText(" " + rowDataList.size() + " rows" + (selectedRowCount == 0? "": " - " + selectedRowCount + " selected rows"));
label.setText(" " + resultSetResult.getRowData().length + " rows" + (selectedRowCount == 0? "": " - " + selectedRowCount + " selected rows"));
}
});
table.addMouseListener(new MouseAdapter() {
@ -737,14 +454,7 @@ public class EditorPane extends JPanel {
if(!e.isPopupTrigger()) {
return;
}
boolean isEditable = false;
try {
if(rs.getConcurrency() == ResultSet.CONCUR_UPDATABLE) {
isEditable = isDBEditable;
}
} catch (SQLException ex) {
isEditable = false;
}
boolean isEditable = resultSetResult.isEditable();
JPopupMenu menu = new JPopupMenu();
int selectedRowCount = table.getSelectedRowCount();
int selectedColumnCount = table.getSelectedColumnCount();
@ -778,14 +488,9 @@ public class EditorPane extends JPanel {
Arrays.sort(selectedRows);
for(int i=selectedRows.length-1; i>=0; i--) {
int row = selectedRows[i];
int dbRow = (Integer)rowDataList.get(row)[0];
try {
rs.absolute(dbRow);
rs.deleteRow();
rowDataList.remove(row);
boolean isSuccess = resultSetResult.deleteRow(row);
if(isSuccess) {
((AbstractTableModel)table.getModel()).fireTableRowsDeleted(row, row);
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
@ -868,7 +573,7 @@ public class EditorPane extends JPanel {
}
statusCountPane.add(label);
southPanel.add(statusCountPane, BorderLayout.WEST);
southPanel.add(new JLabel(Utils.formatDuration(duration) + " - " + Utils.formatDuration(rsDuration)), BorderLayout.EAST);
southPanel.add(new JLabel(Utils.formatDuration(duration) + " - " + Utils.formatDuration(resultSetResult.getResultSetParsingDuration())), BorderLayout.EAST);
resultPane.add(southPanel, BorderLayout.SOUTH);
southPanel.setToolTipText(sql);
resultPane.revalidate();
@ -924,24 +629,12 @@ public class EditorPane extends JPanel {
return resultPane;
}
void closeConnection() {
if (conn != null) {
if (stmt != null) {
try {
stmt.cancel();
} catch (Exception e) {
}
try {
stmt.close();
} catch (Exception e) {
}
void closeLastExecution() {
synchronized (debugger) {
if(lastStatementExecutor != null) {
lastStatementExecutor.stopExecution();
lastStatementExecutor = null;
}
stmt = null;
try {
conn.close();
} catch (Exception e) {
}
conn = null;
}
}
@ -1097,6 +790,66 @@ public class EditorPane extends JPanel {
return wordStart;
}
private final class ResultTableModel extends AbstractTableModel {
private final StatementExecutionResultSetResult resultSetResult;
private ResultTableModel(StatementExecutionResultSetResult resultSetResult) {
this.resultSetResult = resultSetResult;
}
@Override
public String getColumnName(int column) {
return column == 0? "": resultSetResult.getColumnNames()[column - 1].toString();
}
@Override
public int getRowCount() {
return resultSetResult.getRowData().length;
}
@Override
public int getColumnCount() {
return resultSetResult.getColumnNames().length + 1;
}
@Override
public Object getValueAt(int row, int col) {
return col == 0? row + 1: resultSetResult.getRowData()[row][col - 1];
}
@Override
public void setValueAt(Object o, int row, int col) {
resultSetResult.setValueAt(o, row, col - 1);
}
@Override
public boolean isCellEditable(int row, int col) {
return col > 0 && resultSetResult.isEditable();
}
@Override
public Class<?> getColumnClass(int col) {
return col == 0? Integer.class: resultSetResult.getColumnClasses()[col - 1];
}
}
private final class ResultTable extends JTableX {
private ResultTable(StatementExecutionResultSetResult resultSetResult) {
super(new ResultTableModel(resultSetResult));
}
@Override
public TableCellEditor getCellEditor(int row, int column) {
TableCellEditor editor = super.getCellEditor(row, column);
if(editor instanceof DefaultCellEditor) {
DefaultCellEditor defaultEditor = (DefaultCellEditor) editor;
defaultEditor.setClickCountToStart(2);
}
return editor;
}
}
private static enum KeyWordType {
DYNAMIC_STATEMENT,
TABLE,
@ -1124,10 +877,10 @@ public class EditorPane extends JPanel {
List<CompletionCandidate> candidateList = new ArrayList<CompletionCandidate>();
// Here can add more candidates depending on magic word start.
if(candidateList.isEmpty()) {
for(String s: getTableNames()) {
for(String s: debugger.getTableNames()) {
candidateList.add(new CompletionCandidate(KeyWordType.TABLE, s));
}
for(String s: getTableColumnNames()) {
for(String s: debugger.getTableColumnNames()) {
candidateList.add(new CompletionCandidate(KeyWordType.TABLE_COlUMN, s));
}
for(String s: new String[] {
@ -1175,29 +928,6 @@ public class EditorPane extends JPanel {
return filteredCompletionCandidateList.toArray(new CompletionCandidate[0]);
}
private String[] getTableNames() {
List<String> tableNameList = new ArrayList<String>();
for(Table<? extends Record> table: databaseDescriptor.getSchema().getTables()) {
String tableName = table.getName();
tableNameList.add(tableName);
}
Collections.sort(tableNameList, String.CASE_INSENSITIVE_ORDER);
return tableNameList.toArray(new String[0]);
}
private String[] getTableColumnNames() {
Set<String> columnNameSet = new HashSet<String>();
for(Table<?> table: databaseDescriptor.getSchema().getTables()) {
for(Field<?> field: table.getFields()) {
String columnName = field.getName();
columnNameSet.add(columnName);
}
}
String[] columnNames = columnNameSet.toArray(new String[0]);
Arrays.sort(columnNames, String.CASE_INSENSITIVE_ORDER);
return columnNames;
}
public void adjustDefaultFocus() {
editorTextArea.requestFocusInWindow();
}

View File

@ -97,9 +97,9 @@ import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import org.jooq.debug.Debugger;
import org.jooq.debug.DebuggerData;
import org.jooq.debug.DebuggerRegistry;
import org.jooq.debug.DebuggerResultSetData;
import org.jooq.debug.LoggingListener;
import org.jooq.debug.QueryLoggingData;
import org.jooq.debug.ResultSetLoggingData;
import org.jooq.debug.SqlQueryType;
import org.jooq.debug.console.misc.JTableX;
import org.jooq.debug.console.misc.RichTextTransferable;
@ -135,7 +135,7 @@ public class LoggerPane extends JPanel {
private final ImageIcon OTHER_ICON = new ImageIcon(getClass().getResource("/org/jooq/debug/console/resources/SqlOther16.png"));
private final ImageIcon SELECT_ICON = new ImageIcon(getClass().getResource("/org/jooq/debug/console/resources/SqlSelect16.png"));
private Debugger sqlQueryDebugger;
private Debugger debugger;
private JTableX table;
private SqlTextArea textArea;
private JLabel loggerStatusLabel;
@ -147,8 +147,9 @@ public class LoggerPane extends JPanel {
private boolean isOtherQueryTypeDisplayed = true;
private boolean isScrollLocked;
public LoggerPane() {
public LoggerPane(Debugger debugger) {
super(new BorderLayout());
this.debugger = debugger;
setOpaque(false);
JPanel loggerHeaderPanel = new JPanel(new BorderLayout());
loggerHeaderPanel.setOpaque(false);
@ -359,15 +360,15 @@ public class LoggerPane extends JPanel {
return duration < 0? null: duration;
}
case COLUMN_RS_LIFETIME: {
DebuggerResultSetData rsData = queryDebuggingInfo.getSqlQueryDebuggerResultSetData();
ResultSetLoggingData rsData = queryDebuggingInfo.getResultSetLoggingData();
return rsData == null? null: rsData.getLifeTime();
}
case COLUMN_RS_READ: {
DebuggerResultSetData rsData = queryDebuggingInfo.getSqlQueryDebuggerResultSetData();
ResultSetLoggingData rsData = queryDebuggingInfo.getResultSetLoggingData();
return rsData == null? null: rsData.getReadCount();
}
case COLUMN_RS_READ_ROWS: {
DebuggerResultSetData rsData = queryDebuggingInfo.getSqlQueryDebuggerResultSetData();
ResultSetLoggingData rsData = queryDebuggingInfo.getResultSetLoggingData();
return rsData == null? null: rsData.getReadRows();
}
case COLUMN_DUPLICATION_COUNT: {
@ -732,36 +733,36 @@ public class LoggerPane extends JPanel {
private static class QueryDebuggingInfo {
private long timestamp;
private DebuggerData sqlQueryDebuggerData;
private QueryLoggingData queryLoggingData;
private Throwable throwable;
private int duplicationCount;
public QueryDebuggingInfo(long timestamp, DebuggerData sqlQueryDebuggerData) {
public QueryDebuggingInfo(long timestamp, QueryLoggingData queryLoggingData) {
this.timestamp = timestamp;
this.sqlQueryDebuggerData = sqlQueryDebuggerData;
this.queryLoggingData = queryLoggingData;
this.throwable = new Exception("Statement Stack trace");
throwable.setStackTrace(sqlQueryDebuggerData.getCallerStackTraceElements());
throwable.setStackTrace(queryLoggingData.getCallerStackTraceElements());
}
public long getTimestamp() {
return timestamp;
}
public DebuggerData getSqlQueryDebuggerData() {
return sqlQueryDebuggerData;
public QueryLoggingData getQueryLoggingData() {
return queryLoggingData;
}
public Long getPrepardeStatementPreparationDuration() {
return sqlQueryDebuggerData.getPreparedStatementPreparationDuration();
return queryLoggingData.getPreparedStatementPreparationDuration();
}
public Long getPrepardeStatementBindingDuration() {
return sqlQueryDebuggerData.getPreparedStatementBindingDuration();
return queryLoggingData.getPreparedStatementBindingDuration();
}
public long getExecutionDuration() {
return sqlQueryDebuggerData.getExecutionDuration();
return queryLoggingData.getExecutionDuration();
}
public SqlQueryType getQueryType() {
return sqlQueryDebuggerData.getQueryType();
return queryLoggingData.getQueryType();
}
public String[] getQueries() {
String parameterDescription = sqlQueryDebuggerData.getParameterDescription();
String[] queries = sqlQueryDebuggerData.getQueries();
String parameterDescription = queryLoggingData.getParameterDescription();
String[] queries = queryLoggingData.getQueries();
if(parameterDescription != null) {
return new String[] {queries[0] + " -> " + parameterDescription};
}
@ -771,10 +772,10 @@ public class LoggerPane extends JPanel {
return throwable;
}
public String getThreadName() {
return sqlQueryDebuggerData.getThreadName();
return queryLoggingData.getThreadName();
}
public long getThreadId() {
return sqlQueryDebuggerData.getThreadID();
return queryLoggingData.getThreadID();
}
public void setDuplicationCount(int duplicationCount) {
this.duplicationCount = duplicationCount;
@ -782,12 +783,12 @@ public class LoggerPane extends JPanel {
public int getDuplicationCount() {
return duplicationCount;
}
private DebuggerResultSetData sqlQueryDebuggerResultSetData;
public void setSqlQueryDebuggerResultSetData(DebuggerResultSetData sqlQueryDebuggerResultSetData) {
this.sqlQueryDebuggerResultSetData = sqlQueryDebuggerResultSetData;
private ResultSetLoggingData resultSetLoggingData;
public void setResultSetLoggingData(ResultSetLoggingData resultSetLoggingData) {
this.resultSetLoggingData = resultSetLoggingData;
}
public DebuggerResultSetData getSqlQueryDebuggerResultSetData() {
return sqlQueryDebuggerResultSetData;
public ResultSetLoggingData getResultSetLoggingData() {
return resultSetLoggingData;
}
private int displayedRow = -1;
public int getDisplayedRow() {
@ -807,15 +808,11 @@ public class LoggerPane extends JPanel {
this.isLogging = isLogging;
loggerOnButton.setVisible(!isLogging);
loggerOffButton.setVisible(isLogging);
if(sqlQueryDebugger != null) {
DebuggerRegistry.removeSqlQueryDebugger(sqlQueryDebugger);
sqlQueryDebugger = null;
}
if(isLogging) {
sqlQueryDebugger = new Debugger() {
LoggingListener loggingListener = new LoggingListener() {
@Override
public void debugQueries(DebuggerData sqlQueryDebuggerData) {
debugQueries(new QueryDebuggingInfo(System.currentTimeMillis(), sqlQueryDebuggerData));
public void logQueries(QueryLoggingData queryLoggingData) {
debugQueries(new QueryDebuggingInfo(System.currentTimeMillis(), queryLoggingData));
}
public void debugQueries(final QueryDebuggingInfo queryDebuggingInfo) {
if(!SwingUtilities.isEventDispatchThread()) {
@ -830,20 +827,20 @@ public class LoggerPane extends JPanel {
addRow(queryDebuggingInfo);
}
@Override
public void debugResultSet(final int sqlQueryDebuggerDataID, final DebuggerResultSetData sqlQueryDebuggerResultSetData) {
public void logResultSet(final int queryLoggingDataID, final ResultSetLoggingData resultSetLoggingData) {
if(!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
debugResultSet(sqlQueryDebuggerDataID, sqlQueryDebuggerResultSetData);
logResultSet(queryLoggingDataID, resultSetLoggingData);
}
});
return;
}
for(int i=queryDebuggingInfoList.size()-1; i>=0; i--) {
QueryDebuggingInfo queryDebuggingInfo = queryDebuggingInfoList.get(i);
if(queryDebuggingInfo.getSqlQueryDebuggerData().getID() == sqlQueryDebuggerDataID) {
queryDebuggingInfo.setSqlQueryDebuggerResultSetData(sqlQueryDebuggerResultSetData);
if(queryDebuggingInfo.getQueryLoggingData().getID() == queryLoggingDataID) {
queryDebuggingInfo.setResultSetLoggingData(resultSetLoggingData);
XTableColumnModel columnModel = (XTableColumnModel)table.getColumnModel();
boolean isResultSetDataShown = columnModel.isColumnVisible(columnModel.getColumnByModelIndex(COLUMN_RS_LIFETIME));
if(isResultSetDataShown) {
@ -854,7 +851,9 @@ public class LoggerPane extends JPanel {
}
}
};
DebuggerRegistry.addSqlQueryDebugger(sqlQueryDebugger);
debugger.setLoggingListener(loggingListener);
} else {
debugger.setLoggingListener(null);
}
}
@ -909,7 +908,7 @@ public class LoggerPane extends JPanel {
"<th>Stack trace</th>" +
"</tr>\n");
for(QueryDebuggingInfo queryDebuggingInfo: queryDebuggingInfos) {
DebuggerResultSetData resultSetData = queryDebuggingInfo.getSqlQueryDebuggerResultSetData();
ResultSetLoggingData resultSetData = queryDebuggingInfo.getResultSetLoggingData();
htmlSB.append("<tr>\n");
htmlSB.append("<td>");
htmlSB.append(queryDebuggingInfo.getQueryType());

View File

@ -36,7 +36,7 @@
*/
package org.jooq.debug.console.remote;
import org.jooq.debug.DebuggerData;
import org.jooq.debug.QueryLoggingData;
/**
* @author Christopher Deckers
@ -44,13 +44,13 @@ import org.jooq.debug.DebuggerData;
@SuppressWarnings("serial")
public class ClientDebugQueriesMessage implements Message {
private DebuggerData sqlQueryDebuggerData;
private QueryLoggingData sqlQueryDebuggerData;
public ClientDebugQueriesMessage(DebuggerData sqlQueryDebuggerData) {
public ClientDebugQueriesMessage(QueryLoggingData sqlQueryDebuggerData) {
this.sqlQueryDebuggerData = sqlQueryDebuggerData;
}
public DebuggerData getSqlQueryDebuggerData() {
public QueryLoggingData getSqlQueryDebuggerData() {
return sqlQueryDebuggerData;
}

View File

@ -36,7 +36,7 @@
*/
package org.jooq.debug.console.remote;
import org.jooq.debug.DebuggerResultSetData;
import org.jooq.debug.ResultSetLoggingData;
/**
* @author Christopher Deckers
@ -45,9 +45,9 @@ import org.jooq.debug.DebuggerResultSetData;
public class ClientDebugResultSetMessage implements Message {
private int sqlQueryDebuggerDataID;
private DebuggerResultSetData sqlQueryDebuggerResultSetData;
private ResultSetLoggingData sqlQueryDebuggerResultSetData;
public ClientDebugResultSetMessage(int sqlQueryDebuggerDataID, DebuggerResultSetData sqlQueryDebuggerData) {
public ClientDebugResultSetMessage(int sqlQueryDebuggerDataID, ResultSetLoggingData sqlQueryDebuggerData) {
this.sqlQueryDebuggerDataID = sqlQueryDebuggerDataID;
this.sqlQueryDebuggerResultSetData = sqlQueryDebuggerData;
}
@ -56,7 +56,7 @@ public class ClientDebugResultSetMessage implements Message {
return sqlQueryDebuggerDataID;
}
public DebuggerResultSetData getSqlQueryDebuggerResultSetData() {
public ResultSetLoggingData getSqlQueryDebuggerResultSetData() {
return sqlQueryDebuggerResultSetData;
}

View File

@ -44,61 +44,45 @@ import java.io.ObjectOutputStream;
import java.net.Socket;
import org.jooq.debug.Debugger;
import org.jooq.debug.DebuggerData;
import org.jooq.debug.DebuggerRegistry;
import org.jooq.debug.DebuggerRegistryListener;
import org.jooq.debug.DebuggerResultSetData;
import org.jooq.debug.LoggingListener;
import org.jooq.debug.QueryLoggingData;
import org.jooq.debug.ResultSetLoggingData;
import org.jooq.debug.StatementExecutor;
/**
* @author Christopher Deckers
*/
public class RemoteDebuggerClient {
public class RemoteDebuggerClient implements Debugger {
private Socket socket;
private ObjectOutputStream out;
private final Object LOCK = new Object();
public RemoteDebuggerClient(String ip, int port) throws Exception {
socket = new Socket(ip, port);
out = new ObjectOutputStream(new BufferedOutputStream(socket.getOutputStream()));
Thread thread = new Thread("SQL Remote Debugger Client on port " + port) {
@Override
public void run() {
DebuggerRegistryListener debuggerRegisterListener = null;
try {
final ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(socket.getOutputStream()));
debuggerRegisterListener = new DebuggerRegistryListener() {
@Override
public void notifyDebuggerListenersModified() {
try {
boolean isLogging = !DebuggerRegistry.getDebuggerList().isEmpty();
out.writeObject(new ServerLoggingActivationMessage(isLogging));
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
};
DebuggerRegistry.addDebuggerRegisterListener(debuggerRegisterListener);
ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(socket.getInputStream()));
for(Message o; (o=(Message)in.readObject()) != null; ) {
if(o instanceof ClientDebugQueriesMessage) {
DebuggerData sqlQueryDebuggerData = ((ClientDebugQueriesMessage) o).getSqlQueryDebuggerData();
for(Debugger debugger: DebuggerRegistry.getDebuggerList()) {
debugger.debugQueries(sqlQueryDebuggerData);
}
} else if(o instanceof ClientDebugResultSetMessage) {
ClientDebugResultSetMessage m = (ClientDebugResultSetMessage) o;
int sqlQueryDebuggerDataID = m.getSqlQueryDebuggerDataID();
DebuggerResultSetData clientDebugResultSetData = m.getSqlQueryDebuggerResultSetData();
for(Debugger debugger: DebuggerRegistry.getDebuggerList()) {
debugger.debugResultSet(sqlQueryDebuggerDataID, clientDebugResultSetData);
}
}
}
try {
ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(socket.getInputStream()));
for(Message o; (o=(Message)in.readObject()) != null; ) {
if(o instanceof ClientDebugQueriesMessage) {
if(loggingListener != null) {
QueryLoggingData sqlQueryDebuggerData = ((ClientDebugQueriesMessage) o).getSqlQueryDebuggerData();
loggingListener.logQueries(sqlQueryDebuggerData);
}
} else if(o instanceof ClientDebugResultSetMessage) {
if(loggingListener != null) {
ClientDebugResultSetMessage m = (ClientDebugResultSetMessage) o;
int sqlQueryDebuggerDataID = m.getSqlQueryDebuggerDataID();
ResultSetLoggingData clientDebugResultSetData = m.getSqlQueryDebuggerResultSetData();
loggingListener.logResultSet(sqlQueryDebuggerDataID, clientDebugResultSetData);
}
}
}
} catch(Exception e) {
e.printStackTrace();
} finally {
if(debuggerRegisterListener != null) {
DebuggerRegistry.removeDebuggerRegisterListener(debuggerRegisterListener);
}
}
}
};
@ -106,4 +90,54 @@ public class RemoteDebuggerClient {
thread.start();
}
private LoggingListener loggingListener;
@Override
public void setLoggingListener(LoggingListener loggingListener) {
this.loggingListener = loggingListener;
try {
synchronized (LOCK) {
out.writeObject(new ServerLoggingActivationMessage(loggingListener != null));
out.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public LoggingListener getLoggingListener() {
return loggingListener;
}
@Override
public boolean isExecutionSupported() {
// TODO: implement
return false;
}
@Override
public StatementExecutor createStatementExecutor(String sql, int maxRSRowsParsing, int retainParsedRSDataRowCountThreshold) {
// TODO: implement
return null;
}
@Override
public String[] getTableNames() {
// TODO: implement
return null;
}
@Override
public String[] getTableColumnNames() {
// TODO: implement
return null;
}
@Override
public boolean isReadOnly() {
// TODO: implement
return false;
}
}

View File

@ -45,9 +45,11 @@ import java.net.ServerSocket;
import java.net.Socket;
import org.jooq.debug.Debugger;
import org.jooq.debug.DebuggerData;
import org.jooq.debug.DebuggerRegistry;
import org.jooq.debug.DebuggerResultSetData;
import org.jooq.debug.LocalDebugger;
import org.jooq.debug.LoggingListener;
import org.jooq.debug.QueryLoggingData;
import org.jooq.debug.ResultSetLoggingData;
/**
* @author Christopher Deckers
@ -88,38 +90,40 @@ public class RemoteDebuggerServer {
Thread clientThread = new Thread("SQL Remote Debugger Server on port " + port) {
@Override
public void run() {
Debugger sqlQueryDebugger = null;
Debugger debugger = null;
boolean isLogging = false;
try {
ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(socket.getInputStream()));
final ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(socket.getOutputStream()));
sqlQueryDebugger = new Debugger() {
@Override
public synchronized void debugQueries(DebuggerData sqlQueryDebuggerData) {
try {
out.writeObject(new ClientDebugQueriesMessage(sqlQueryDebuggerData));
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public synchronized void debugResultSet(int sqlQueryDebuggerDataID, DebuggerResultSetData sqlQueryDebuggerResultSetData) {
try {
out.writeObject(new ClientDebugResultSetMessage(sqlQueryDebuggerDataID, sqlQueryDebuggerResultSetData));
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
};
// TODO: find how to pass a database descriptor for remote edition.
debugger = new LocalDebugger(null);
DebuggerRegistry.addSqlQueryDebugger(debugger);
for(Message o; (o=(Message)in.readObject()) != null; ) {
if(o instanceof ServerLoggingActivationMessage) {
isLogging = ((ServerLoggingActivationMessage) o).isLogging();
if(isLogging) {
DebuggerRegistry.addSqlQueryDebugger(sqlQueryDebugger);
debugger.setLoggingListener(new LoggingListener() {
@Override
public void logQueries(QueryLoggingData queryLoggingData) {
try {
out.writeObject(new ClientDebugQueriesMessage(queryLoggingData));
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void logResultSet(int sqlQueryDebuggerDataID, ResultSetLoggingData resultSetLoggingData) {
try {
out.writeObject(new ClientDebugResultSetMessage(sqlQueryDebuggerDataID, resultSetLoggingData));
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
});
} else {
DebuggerRegistry.removeSqlQueryDebugger(sqlQueryDebugger);
debugger.setLoggingListener(null);
}
}
}
@ -128,8 +132,8 @@ public class RemoteDebuggerServer {
e.printStackTrace();
}
} finally {
if(sqlQueryDebugger != null) {
DebuggerRegistry.removeSqlQueryDebugger(sqlQueryDebugger);
if(debugger != null) {
DebuggerRegistry.removeSqlQueryDebugger(debugger);
}
}
}