SQL-Console: added support for substitution variables

git-svn-id: https://svn.code.sf.net/p/jailer/code/trunk@1604 3dd849cd-670e-4645-a7cd-dd197c8d0e81
This commit is contained in:
rwisser
2018-04-04 06:09:34 +00:00
parent dcf8f46781
commit fa6a06293a
5 changed files with 193 additions and 6 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
7.6.7
- SQL-Console: added support for variables.
- SQL-Console: added support for substitution variables.
7.6.6
- Improved SQL Outline rendering.
@@ -89,9 +89,11 @@ public class MemorizedResultSet implements ResultSet {
throws SQLException {
this.rowList = new ArrayList<Object[]>();
ResultSetMetaData rmd = resultSet.getMetaData();
prepareHook(rmd);
CellContentConverter cellContentConverter = new CellContentConverter(rmd, session, session.dbms);
final int numCol = projection == null? rmd.getColumnCount() : projection.length;
while (resultSet.next()) {
readRowHook(resultSet);
Object[] row = new Object[numCol];
for (int i = 1; i <= numCol; ++i) {
row[i - 1] = convertCellContent(cellContentConverter.getObject(resultSet, projection == null? i : projection[i - 1]));
@@ -114,6 +116,12 @@ public class MemorizedResultSet implements ResultSet {
resultSetMetaData = new MemorizedResultSetMetaData(numCol, names, types);
}
protected void prepareHook(ResultSetMetaData rmd) throws SQLException {
}
protected void readRowHook(ResultSet resultSet) throws SQLException {
}
protected Object convertCellContent(Object object) {
return object;
}
@@ -144,7 +144,7 @@ public abstract class SQLConsole extends javax.swing.JPanel {
private final AtomicBoolean updatingStatus = new AtomicBoolean(false);
private final ImageIcon scaledCancelIcon;
private final ImageIcon scaledExplainIcon;
private final VariableSupport variableSupport = new VariableSupport();
private final SQLPlusSupport sqlPlusSupport = new SQLPlusSupport();
/**
* Creates new form SQLConsole
@@ -260,7 +260,11 @@ public abstract class SQLConsole extends javax.swing.JPanel {
});
restoreHistory();
provider = new MetaDataBasedSQLCompletionProvider(session, metaDataSource);
provider = new MetaDataBasedSQLCompletionProvider(session, metaDataSource) {
protected String prepareStatementForAliasAnalysis(String statement) {
return sqlPlusSupport.replaceVariables(statement, null);
}
};
new SQLAutoCompletion(provider, editorPane);
RTextScrollPane jScrollPane = new RTextScrollPane();
@@ -561,7 +565,7 @@ public abstract class SQLConsole extends javax.swing.JPanel {
CancellationHandler.begin(statement, SQLConsole.this);
long startTime = System.currentTimeMillis();
sqlStatement = sql.replaceFirst("(?is)(;\\s*)+$", "");
sqlStatement = variableSupport.replaceVariables(sqlStatement, positionOffsets);
sqlStatement = sqlPlusSupport.replaceVariables(sqlStatement, positionOffsets);
boolean hasResultSet;
boolean isDefine = false;
if (explain) {
@@ -575,7 +579,7 @@ public abstract class SQLConsole extends javax.swing.JPanel {
statement = session.getConnection().createStatement();
hasResultSet = statement.execute(String.format(session.dbms.getExplainQuery(), sqlStatement, stmtId));
} else {
if (variableSupport.executeDefine(sqlStatement)) {
if (sqlPlusSupport.executeSQLPLusStatement(sqlStatement)) {
isDefine = true;
hasResultSet = false;
} else {
@@ -622,6 +626,20 @@ public abstract class SQLConsole extends javax.swing.JPanel {
}
return object;
}
@Override
protected void prepareHook(ResultSetMetaData rmd) throws SQLException {
sqlPlusSupport.prepareColumnSubstitution(rmd);
}
@Override
protected void readRowHook(ResultSet resultSet) throws SQLException {
try {
sqlPlusSupport.substituteColumns(resultSet);
} catch (SQLException e) {
// ignore
}
}
};
resultSet.close();
long now = System.currentTimeMillis();
@@ -0,0 +1,155 @@
/*
* Copyright 2007 - 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.jailer.ui.databrowser.sqlconsole;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import net.sf.jailer.ui.databrowser.metadata.MetaDataDetailsPanel;
/**
* Supports some Oracle SQL+ statements.
*
* @author Ralf Wisser
*/
public class SQLPlusSupport {
/**
* The logger.
*/
private static final Logger logger = Logger.getLogger(MetaDataDetailsPanel.class);
/**
* The variables.
*/
private Map<String, String> variables = new HashMap<String, String>();
/**
* Column substitutions.
*/
private Map<String, String[]> columnSubstitutions = new HashMap<String, String[]>();
private Pattern DEFINE_PATTERN = Pattern.compile("\\s*DEFINE\\s+(\\w+)\\s*=\\s*(.*)\\s*", Pattern.CASE_INSENSITIVE);
private Pattern UNDEFINE_PATTERN = Pattern.compile("\\s*UNDEFINE\\s+((?:\\w+\\s*)+)", Pattern.CASE_INSENSITIVE);
private Pattern COLUMN_PATTERN = Pattern.compile("\\s*COLUMN\\s+(\\w+)\\s*((?:(?:new_value|old_value)\\s+\\w+\\s*)+)", Pattern.CASE_INSENSITIVE);
/**
* Executes a SQLPlus statement.
*
* @param statement the statement
* @return <code>true</code> if statement is a valid SQLPlus statement
*/
public boolean executeSQLPLusStatement(String statement) {
Matcher matcher = DEFINE_PATTERN.matcher(statement);
if (matcher.matches()) {
String var = matcher.group(1);
String val = matcher.group(2);
if (val.length() > 1 && val.startsWith("\"") && val.endsWith("\"")) {
val = val.substring(1, val.length() - 1);
}
variables.put(var.toUpperCase(), val);
logger.info("DEFINE " + var + " = " + val);
return true;
}
matcher = UNDEFINE_PATTERN.matcher(statement);
if (matcher.matches()) {
for (String var: matcher.group(1).split("\\s+")) {
variables.remove(var.toUpperCase());
}
return true;
}
matcher = COLUMN_PATTERN.matcher(statement);
if (matcher.matches()) {
String column = matcher.group(1);
String[] variables = matcher.group(2).split("(?i:\\s*(new_value|old_value)\\s*)");
columnSubstitutions.put(column.toUpperCase(), variables);
return true;
}
return false;
}
/**
* Replaces all occurrences of "&amp;&lt;var&gt;[.]" with variable values.
*
* @param statement the statement
* @param positionOffsets map to put position offsets into
* @return statement with variable replacements
*/
public String replaceVariables(String statement, SortedMap<Integer, Integer> positionOffsets) {
if (!variables.isEmpty()) {
Matcher matcher = Pattern.compile("&(\\w+)(\\.|\\b)").matcher(statement);
matcher.reset();
int offset = 0;
boolean result = matcher.find();
if (result) {
StringBuffer sb = new StringBuffer();
do {
String var = matcher.group(1);
String replacement = variables.get(var.toUpperCase());
if (replacement != null) {
matcher.appendReplacement(sb, replacement);
if (positionOffsets != null) {
offset += matcher.group().length() - replacement.length();
for (int i = 0; i < replacement.length(); ++i) {
positionOffsets.put(sb.length() - replacement.length() + i, offset + (replacement.length() - i - 2));
}
}
}
result = matcher.find();
} while (result);
matcher.appendTail(sb);
return sb.toString();
}
}
return statement;
}
private Map<Integer, String[]> varsPerIndex = new HashMap<Integer, String[]>();
public void prepareColumnSubstitution(ResultSetMetaData metaData) throws SQLException {
varsPerIndex.clear();
if (!columnSubstitutions.isEmpty()) {
for (Entry<String, String[]> e: columnSubstitutions.entrySet()) {
for (int i = 1; i <= metaData.getColumnCount(); ++i) {
if (e.getKey().equalsIgnoreCase(metaData.getColumnLabel(i))) {
varsPerIndex.put(i, e.getValue());
}
}
}
}
}
public void substituteColumns(ResultSet resultSet) throws SQLException {
if (!varsPerIndex.isEmpty()) {
for (Entry<Integer, String[]> e: varsPerIndex.entrySet()) {
String value = resultSet.getString(e.getKey());
for (String var: e.getValue()) {
variables.put(var.toUpperCase(), value);
}
}
}
}
}
@@ -802,6 +802,8 @@ public abstract class SQLCompletionProvider<SOURCE, SCHEMA, TABLE> extends Defau
final int MAX_OUTLINE_INFOS = 500;
Map<String, String> scopeDescriptionPerLastKeyword = new HashMap<String, String>();
statement = prepareStatementForAliasAnalysis(statement);
scopeDescriptionPerLastKeyword.put("select", "Select");
scopeDescriptionPerLastKeyword.put("from", "From");
scopeDescriptionPerLastKeyword.put("with", "With");
@@ -1155,7 +1157,11 @@ public abstract class SQLCompletionProvider<SOURCE, SCHEMA, TABLE> extends Defau
return aliases;
}
public void mergeOutlineInfos(List<OutlineInfo> outlineInfos, int endIndex) {
protected String prepareStatementForAliasAnalysis(String statement) {
return statement;
}
public void mergeOutlineInfos(List<OutlineInfo> outlineInfos, int endIndex) {
// merge "select from dual"
if (outlineInfos != null && endIndex >= 2
&& "from".equalsIgnoreCase(outlineInfos.get(endIndex - 2).scopeDescriptor)