/* * JBoss, Home of Professional Open Source. * * See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing. * * See the AUTHORS.txt file distributed with this work for a full listing of individual contributors. */ package org.teiid.query.ui.sqleditor.component; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.emf.ecore.EObject; import org.teiid.core.designer.util.CoreStringUtil; import org.teiid.designer.core.ModelerCore; import org.teiid.designer.core.index.Block; import org.teiid.designer.core.query.QueryValidationResult; import org.teiid.designer.core.query.QueryValidator; import org.teiid.designer.core.query.QueryValidator.ElementSymbolOptimization; import org.teiid.designer.query.IQueryFactory; import org.teiid.designer.query.IQueryService; import org.teiid.designer.query.sql.ISQLConstants; import org.teiid.designer.query.sql.lang.ICommand; import org.teiid.designer.query.sql.lang.IExpression; import org.teiid.designer.query.sql.lang.IFrom; import org.teiid.designer.query.sql.lang.ILanguageObject; import org.teiid.designer.query.sql.lang.IQuery; import org.teiid.designer.query.sql.lang.ISelect; import org.teiid.designer.query.sql.lang.IUnaryFromClause; import org.teiid.designer.query.sql.lang.util.CommandHelper; import org.teiid.designer.query.sql.symbol.IMultipleElementSymbol; import org.teiid.designer.query.sql.symbol.ISymbol; import org.teiid.query.ui.UiConstants; /** * The <code>QueryDisplayComponent</code> class is the top-level component for * the various types of queries. The user can setText on this component, and the * text will be parsed, reconciled and optimized into the appropriate type of SQL * query. If the string is not recognized, it is stored as "Unknown". There are * various accessor methods to get information about the parsed query. * * @since 8.0 */ public class QueryDisplayComponent implements DisplayNodeConstants, UiConstants { // =========================================================================================================================== // Constants private static final String DEFAULT_QUERY = Util.getString("QueryDisplayComponent.defaultSqlText"); //$NON-NLS-1$ //private static final String QUERY_PARSABLE_MSG = Util.getString("QueryDisplayComponent.queryParsableMsg"); //$NON-NLS-1$ private static final String QUERY_NOT_PARSABLE_MSG = Util.getString("QueryDisplayComponent.queryNotParsableMsg"); //$NON-NLS-1$ private static final String QUERY_PARSABLE_NOT_RESOLVABLE_MSG = Util.getString("QueryDisplayComponent.queryParsableNotResolvableMsg"); //$NON-NLS-1$ private static final String QUERY_NOT_VALID_MSG = Util.getString("QueryDisplayComponent.queryNotValidMsg"); //$NON-NLS-1$ //private static final String QUERY_VALID_MSG = Util.getString("QueryDisplayComponent.queryValidMsg"); //$NON-NLS-1$ private static final String RESOLVER_ERROR_MSG = Util.getString("QueryDisplayComponent.resolverErrorMsg"); //$NON-NLS-1$ private static final String EMPTY_SQL_MSG = Util.getString("QueryDisplayComponent.emptySqlMsg"); //$NON-NLS-1$ private static final String DEFAULT_SQL_MSG = Util.getString("QueryDisplayComponent.defaultSqlMsg"); //$NON-NLS-1$ private static final String MONITOR_CHECKING_DISPLAY_NODES = Util.getString("QueryDisplayComponent.checkingDisplayNodes"); //$NON-NLS-1$ private static final String MONITOR_VALIDATING_SQL = Util.getString("QueryDisplayComponent.validatingSQL"); //$NON-NLS-1$ private static final String MONITOR_PREPARING_RESULTS = Util.getString("QueryDisplayComponent.preparingResults"); //$NON-NLS-1$ // =========================================================================================================================== // Variables /** QueryValidator **/ private QueryValidator queryValidator = null; /** External MetadataMap, for resolving SQL */ //private Map externalMetadataMap = Collections.EMPTY_MAP; private int queryType = QueryValidator.SELECT_TRNS; private QueryValidationResult validationResult; /** Parsable Status */ private boolean isParsable = false; /** Resolvable Status */ private boolean isResolvable = false; /** Validatable Status */ private boolean isValidatable = false; /** Use External Map status */ //private boolean useExternalMetadataMap = false; /** StatusMessage for the current displayComponent */ private String statusMessage = null; /** Highlight startIndex for error highlighting */ private int errorStart = -1; /** Highlight endIndex for error highlighting */ private int errorEnd = -1; /** DisplayNode for the display Component */ private DisplayNode sqlDisplayNode = null; /** Command LanguageObject corresponding to the current DisplayNode, if any */ private ICommand command = null; /** Optimizer On status */ //TODO: allow for default optimization private boolean optimizerOn = false; /** Optimized status */ private ElementSymbolOptimization optimized = ElementSymbolOptimization.UNMODIFIED; private PropertyChangeSupport propChgSupport = new PropertyChangeSupport(this); // ------------------------------------------------------------------------------------------------------------------- // DEFECT 23230 // We need to cache the sqlText here to maintain the last setText() value // ------------------------------------------------------------------------------------------------------------------- private String sqlText = null; //private static int nValidations = 0; // =========================================================================================================================== // Constructors /** * QueryDisplayComponent constructor * @param resolver the resolver to use in resolving the query. */ public QueryDisplayComponent(QueryValidator queryValidator, int type) { this.queryValidator = queryValidator; this.queryType = type; } // =========================================================================================================================== // Methods public void addPropertyListener(PropertyChangeListener listener) { this.propChgSupport.addPropertyChangeListener(listener); } public void removePropertyListener(PropertyChangeListener listener) { this.propChgSupport.removePropertyChangeListener(listener); } /** * Set the QueryValidator * @param validator the query validator */ public void setQueryValidator(QueryValidator validator) { this.queryValidator = validator; this.validationResult = null; } private void setText(String sqlString) { setText(sqlString, true, null, new NullProgressMonitor()); } /** * Set the text for this QueryDisplayComponent. * @param sqlString the SQL text string */ public void setText(String sqlString, boolean doResolveAndValidate, QueryValidationResult theResult) { this.setText(sqlString, doResolveAndValidate, theResult, new NullProgressMonitor()); } /** * Set the text for this QueryDisplayComponent. * @param sqlString the SQL text string */ public void setText(String sqlString, boolean doResolveAndValidate, QueryValidationResult theResult, IProgressMonitor monitor) { // ------------------------------------------------------------------------------------------------------------------- // DEFECT 23230 // Added String check here to prevent unnecessary validation if SQL doesn't change. // ------------------------------------------------------------------------------------------------------------------- boolean setSqlText = false; if( isEmptySql(sqlText) || // sqlText == null || sqlText.length() == 0 || !sqlText.equals(sqlString) ) { setSqlText = true; } if( setSqlText ) { sqlText = sqlString; // this does full validation and resets internal variables validateSql(sqlString, doResolveAndValidate, theResult, monitor); } } public boolean isDeoptimized() { return this.optimized == ElementSymbolOptimization.DEOPTIMIZED; } public void reset() { this.sqlDisplayNode = null; this.sqlText = null; } private void validateSql(String inputSqlString, boolean doResolveAndValidate, QueryValidationResult theResult) { this.validateSql(inputSqlString, doResolveAndValidate, theResult, new NullProgressMonitor()); } private void validateSql(String inputSqlString, boolean doResolveAndValidate, QueryValidationResult theResult, IProgressMonitor monitor) { //Stopwatch watch = new Stopwatch(); //watch.start(); // ------------------------------------------------------------------------------------------------------------------- // DEFECT 23230 // SQL Editor input may be NULL, so the query validator may be null // ------------------------------------------------------------------------------------------------------------------- String sqlString = inputSqlString; if( sqlString == null || isEmptySql(sqlString)) { sqlString = BLANK; } if( queryValidator == null || !queryValidator.isValidRoot()) { // Defect 23421 - in the case of a "delete operation", the sqlString is set to "" and we need to create a display // node to update/clear the sql panel text. sqlDisplayNode = DisplayNodeFactory.createUnknownQueryDisplayNode(null,BLANK); return; } if( !this.queryValidator.shouldValidate() ) { return; } monitor.subTask(MONITOR_CHECKING_DISPLAY_NODES); monitor.worked(5); //nValidations++; // Replace sqlString with the combined toSting() values of sqlDisplayNode's display node list, replacing the visible // nodes' text with the current value of sqlString. if (!sqlString.trim().toUpperCase().startsWith(ISQLConstants.SQL_TYPE_CREATE_STRING) && this.sqlDisplayNode != null) { StringBuffer text = new StringBuffer(); boolean replaced = false; for (Iterator iter = this.sqlDisplayNode.getDisplayNodeList().iterator(); iter.hasNext();) { DisplayNode node = (DisplayNode)iter.next(); if (node.isVisible()) { if (!replaced) { text.append(sqlString); replaced = true; } } else { if (!replaced && node.getParent().languageObject instanceof Block && ISQLConstants.END.equals(node.toString())) { text.append(sqlString); } text.append(node.toString()); } } // for sqlString = text.toString(); } Collection statusList = Collections.EMPTY_LIST; monitor.subTask(MONITOR_VALIDATING_SQL); monitor.worked(5); if( !doResolveAndValidate && theResult != null) { this.validationResult = theResult; } else { try { ElementSymbolOptimization status = ElementSymbolOptimization.UNMODIFIED; if (this.optimizerOn) { status = ElementSymbolOptimization.OPTIMIZED; } else { status = ElementSymbolOptimization.DEOPTIMIZED; } queryValidator.setElementSymbolOptimization(status); this.validationResult = queryValidator.validateSql(sqlString, this.queryType , false); this.optimized = status; } catch (Exception ex) { sqlDisplayNode = DisplayNodeFactory.createUnknownQueryDisplayNode(null,sqlString); StringBuffer buff = new StringBuffer("Error encountered while validating the transformation."); //$NON-NLS-1$ buff.append(CR+"Please check the Message log for exceptions"); //$NON-NLS-1$ statusMessage = buff.toString(); UiConstants.Util.log(IStatus.ERROR,ex, statusMessage); setCommand(null); return; } } monitor.subTask(MONITOR_PREPARING_RESULTS); monitor.worked(20); this.isParsable = validationResult.isParsable(); this.isResolvable = validationResult.isResolvable(); this.isValidatable = validationResult.isValidatable(); statusList= validationResult.getStatusList(); Collection moreStatus = Collections.EMPTY_LIST; if( this.queryType == QueryValidator.INSERT_TRNS ) { moreStatus = validationResult.getUpdateStatusList(QueryValidator.INSERT_TRNS); } else if( this.queryType == QueryValidator.UPDATE_TRNS ) { moreStatus = validationResult.getUpdateStatusList(QueryValidator.UPDATE_TRNS); } else if( this.queryType == QueryValidator.DELETE_TRNS ) { moreStatus = validationResult.getUpdateStatusList(QueryValidator.DELETE_TRNS); } if( moreStatus != null && !moreStatus.isEmpty() ) { statusList = new ArrayList(statusList); statusList.addAll(moreStatus); } setCommand(validationResult.getCommand()); if (!doResolveAndValidate && runOptimizeDeoptimize()) { return; } if(!this.isParsable) { setCommand(null); sqlDisplayNode = DisplayNodeFactory.createUnknownQueryDisplayNode(null,sqlString); // SQL is empty if( isEmptySql(sqlString) ) { statusMessage = EMPTY_SQL_MSG; // SQL is default (SELECT * FROM) } else if( isDefaultSql(sqlString) ) { statusMessage = DEFAULT_SQL_MSG; } else { StringBuffer buff = new StringBuffer(QUERY_NOT_PARSABLE_MSG); if(statusList!=null && !statusList.isEmpty()) { Iterator iter = statusList.iterator(); while(iter.hasNext()) { IStatus status = (IStatus)iter.next(); buff.append(CR+status.getMessage()); } } statusMessage = buff.toString(); } } else { sqlDisplayNode = DisplayNodeFactory.createDisplayNode(null,getCommand()); this.sqlText = sqlDisplayNode.toDisplayString(); if(!this.isResolvable) { StringBuffer buff = new StringBuffer(QUERY_PARSABLE_NOT_RESOLVABLE_MSG); if(statusList!=null && !statusList.isEmpty()) { Iterator iter = statusList.iterator(); while(iter.hasNext()) { IStatus status = (IStatus)iter.next(); buff.append(CR+RESOLVER_ERROR_MSG+COLON+SPACE+status.getMessage()); } } statusMessage = buff.toString(); } else if(!this.isValidatable) { StringBuffer buff = new StringBuffer(QUERY_NOT_VALID_MSG); if(statusList!=null && !statusList.isEmpty()) { Iterator iter = statusList.iterator(); while(iter.hasNext()) { IStatus status = (IStatus)iter.next(); buff.append(CR+status.getMessage()); } } statusMessage = buff.toString(); } else { statusMessage = BLANK; } } sqlDisplayNode.setStartIndex(0); this.propChgSupport.firePropertyChange(null, null, null); //watch.stopPrintIncrementAndRestart("QueryDisplayComponent.validateSql() took "); //System.out.println(" >> SQL = \n " + sqlString); } /** * Method to toggle the Optimizer on or off. * @param status 'true' to enable the optimizer, 'false' to disable it. */ public void setOptimizerOn(boolean status) { this.optimizerOn = status; runOptimizeDeoptimize(); } /** * @return true if a new sql text was set */ private boolean runOptimizeDeoptimize() { if( !canOptimize()) { return false; } if (this.isOptimizerOn()) { if (this.optimized != ElementSymbolOptimization.OPTIMIZED) { validateSql(getCommand().toString(), true, null); return true; } } else if (this.optimized != ElementSymbolOptimization.DEOPTIMIZED) { // --------------------- // DEFECT 23230 // Cache the original command string so we can do the "is changed" check // --------------------- String optimizedSql = getCommand().toString(); IQueryService queryService = ModelerCore.getTeiidQueryService(); queryService.fullyQualifyElements(getCommand()); this.optimized = ElementSymbolOptimization.DEOPTIMIZED; // -------------------------------------------------------------------------------------------------------------- // DEFECT 23230 // deoptimization may NOT change the SQL string, so we don't want to do a second validation if the SQL did NOT change // -------------------------------------------------------------------------------------------------------------- if( optimizedSql != null && !optimizedSql.equalsIgnoreCase(getCommand().toString())) { validateSql(getCommand().toString(), true, null); return true; } } return false; } /** * Check the Optimizer On/Off status. * @return 'true' if the optimizer is on, 'false' if not. */ public boolean isOptimizerOn() { return this.optimizerOn; } /** * Determine if the optimizer can be used for the current SQL. * @return 'true' if the optimizer can be used, 'false' is not. */ public boolean canOptimize() { // If no Optimizer set or not resolvable, cannot optimize. return isResolvable(); } /** * Get the displayNode representing this QueryDisplayComponent. * @return the DisplayNode */ public DisplayNode getDisplayNode() { return sqlDisplayNode; } /** * Get the list of all displayNodes representing this QueryDisplayComponent. * @return the DisplayNode List */ public List getDisplayNodeList() { if(sqlDisplayNode!=null) { return sqlDisplayNode.getDisplayNodeList(); } return Collections.EMPTY_LIST; } /** * Get the parsable status for this QueryDisplayComponent. * @return 'true' if Parsable, 'false' if not. */ public boolean isParsable() { return isParsable; } /** * Get the resolvable status for this QueryDisplayComponent. * @return 'true' if Resolvable, 'false' if not. */ public boolean isResolvable() { return isResolvable; } /** * Get the validatable status for this QueryDisplayComponent. * @return 'true' if Validatable, 'false' if not. */ public boolean isValidatable() { return isValidatable; } /** * Check whether this QueryDisplayComponent is the Default Query. * @return 'true' if default query, 'false' if not. */ public boolean isDefaultQuery() { return isDefaultSql(this.toDisplayString()); } /** * Check whether the provided SQL string is the Default Query. * @return 'true' if default sql, 'false' if not. */ private boolean isDefaultSql(String sqlString) { StringBuffer sb = new StringBuffer(sqlString); CoreStringUtil.replaceAll(sb,CR,BLANK); CoreStringUtil.replaceAll(sb,TAB,SPACE); CoreStringUtil.replaceAll(sb,DBLSPACE,SPACE); String newString = sb.toString(); if(newString!=null && DEFAULT_QUERY!=null && newString.trim().equalsIgnoreCase(DEFAULT_QUERY.trim())) { return true; } return false; } /** * Check whether this QueryDisplayComponent is the Empty Query. * @return 'true' if empty query, 'false' if not. */ public boolean isEmptyQuery() { return isEmptySql(sqlText); } private boolean isEmptySql(String sqlString) { return sqlString == null || sqlString.trim().length() == 0; } /** * Get the number of projected symbols for the current query. * @return the number of projected symbols. */ public int getProjectedSymbolCount() { if(getCommand()!=null) { List<IExpression> symbols = CommandHelper.getProjectedSymbols(getCommand()); return symbols.size(); } return 0; } /** * Tests whether this query is a SELECT *. * @return true if the query is a SELECT *, false if not. */ public boolean isSelectStar() { boolean isSelectStar = false; if(isParsable && getCommand()!=null && getCommand() instanceof IQuery) { ISelect select = ((IQuery)getCommand()).getSelect(); if(select!=null) { isSelectStar = select.isStar(); } } return isSelectStar; } /** * Expand the Query SELECT *. If the current query is a SELECT *, it is expanded. */ public void expandSelect() { ICommand theCommand = getCommand(); if(isSelectStar() && theCommand instanceof IQuery) { IQuery query = (IQuery)theCommand; // If ANY of the Select Symbols are Multi-Element Symbols, expand boolean expandSelect = false; List syms = query.getSelect().getSymbols(); for(int i=0; i<syms.size(); i++) { if(syms.get(i) instanceof IMultipleElementSymbol) { expandSelect = true; break; } } // expand Select if required if(expandSelect) { // Get the list of SELECT symbols List<IExpression> symbols = CommandHelper.getProjectedSymbols(query); StringBuffer selectStr = new StringBuffer(SELECT_STR+SPACE); for(int i=0; i<symbols.size(); i++) { if(i!=0) selectStr.append(COMMA+SPACE); String symbolName = symbols.get(i).toString(); selectStr.append(symbolName); } if(symbols.size()>0) selectStr.append(SPACE+CR); replaceSelect(selectStr.toString()); } } } /** * Test whether the query at the supplied cursor location can be expanded. If the * cursor is not within a query, returns false. * @param theIndex the cursor index to test. * @return true if the query can be expanded, false if not. */ public boolean canExpandSelect(int index) { // Get the command displayNode at the cursor index DisplayNode commandNode = getCommandDisplayNodeAtIndex(index); if(isParsable && commandNode instanceof QueryDisplayNode) { // command DisplayNode is a QueryDisplayNode return canExpand((IQuery)commandNode.getLanguageObject()); } return false; } /** * Expand the Query that the cursor is in. If the cursor is not within a query, * no action will be taken. * @param index the cursor index. */ public void expandSelect(int index) { if( !canExpandSelect(index) ) { return; } DisplayNode commandDisplayNode = getCommandDisplayNodeAtIndex(index); if(commandDisplayNode!=null && commandDisplayNode instanceof QueryDisplayNode) { QueryDisplayNode queryDisplayNode = (QueryDisplayNode)commandDisplayNode; SelectDisplayNode selectDisplayNode = (SelectDisplayNode)queryDisplayNode.getClauseDisplayNode(SELECT); IQuery query = (IQuery)queryDisplayNode.getLanguageObject(); // expand Select // Get the list of SELECT symbols List<IExpression> symbols = CommandHelper.getProjectedSymbols(query); StringBuffer selectStr = new StringBuffer(SPACE); for(int i=0; i<symbols.size(); i++) { if(i!=0) selectStr.append(COMMA+SPACE); String symbolName = symbols.get(i).toString(); selectStr.append(symbolName); } if(symbols.size()>0) selectStr.append(SPACE+CR); int startIndex = selectDisplayNode.getStartIndex(); int endIndex = selectDisplayNode.getEndIndex(); replace(startIndex, endIndex+1, selectStr.toString()); } } /** * Get the current Query command. * @return the query command. */ public ICommand getCommand() { return command; } private void setCommand(ICommand theCommand) { command = theCommand; } /** * Get the current Status message string for this display component. * @return the error message. */ public String getStatusMessage() { return statusMessage; } /** * Get the start index for this QueryDisplayComponent. * @return the starting index. */ public int getErrorStart() { return errorStart; } /** * Get the ending index for this QueryDisplayComponent. * @return the ending index. */ public int getErrorEnd() { return errorEnd; } /** * Returns the query clause DisplayNode at a given index. If this component is not * a QueryDisplayNode or SetQueryDisplayNode, will return null. * @param the cursor index. * @return the query Clause that the cursor is within, null if not. */ public DisplayNode getQueryClauseAtIndex(int index) { DisplayNode node; if(sqlDisplayNode!=null && sqlDisplayNode instanceof QueryDisplayNode) { node = ((QueryDisplayNode)sqlDisplayNode).getClauseAtIndex(index); } else if(sqlDisplayNode!=null && sqlDisplayNode instanceof SetQueryDisplayNode) { node = ((SetQueryDisplayNode)sqlDisplayNode).getClauseAtIndex(index); } else { node = null; } if (node == null) { UiConstants.Util.log(IStatus.ERROR, null, "DisplayNode: " + sqlDisplayNode.getClass() + "generated null result at index " + index); //$NON-NLS-1$ //$NON-NLS-2$ } return node; } /** * Returns the DisplayNode for the command at a given index. If the index is within * a SubQuery, the subQuery is returned. If not within a SubQuery, the non-SetQuery Command (that is a specific branch) that the * index is within is returned. * @param index the cursor index. * @return the command DisplayNode that the cursor is within. */ public DisplayNode getCommandDisplayNodeAtIndex(int index) { DisplayNode result = getCommandDisplayNodeAtIndexIncludingSetQueries(index); if ( result instanceof SetQueryDisplayNode ) { // Get the query at the index DisplayNode child = ((SetQueryDisplayNode)result).getQueryAtIndex(index); // we may be at the start of the WITH clause, which will return null if (child != null) { result = child; } } return result; } private DisplayNode getCommandDisplayNodeAtIndexIncludingSetQueries(int index) { DisplayNode commandDisplayNode = null; List nodes = getDisplayNodesAtIndex(index); //-------------------------------------------------- // Index is between nodes, look at both //-------------------------------------------------- if(nodes.size()==2) { // Get the index nodes DisplayNode node1 = (DisplayNode)nodes.get(0); DisplayNode node2 = (DisplayNode)nodes.get(1); // If second node is within a SubQuery, return SubQuery node commandDisplayNode = DisplayNodeUtils.getCommandForNode(node2); // If node2 resulted in null, try first node if(commandDisplayNode==null) { commandDisplayNode = DisplayNodeUtils.getCommandForNode(node1); } //-------------------------------------------------- // Index is within a node //-------------------------------------------------- } else if(nodes.size()==1) { // return command for the node commandDisplayNode = DisplayNodeUtils.getCommandForNode((DisplayNode)nodes.get(0)); } return commandDisplayNode; } /** * Returns the QueryDisplayNode index for a given cursor index. Will return -1 if the * index is not within a QueryDisplayNode. * @param the cursor index. * @return the query index that the cursor is within, -1 if not within a query. */ public int getQueryIndex(int index) { QueryDisplayNode queryDisplayNode = null; if(sqlDisplayNode!=null && sqlDisplayNode instanceof QueryDisplayNode) { // If a query, will have one QueryDisplayNode queryDisplayNode = (QueryDisplayNode)sqlDisplayNode; if(queryDisplayNode.isAnywhereWithin(index)) { return 0; } } else if(sqlDisplayNode!=null && sqlDisplayNode instanceof SetQueryDisplayNode) { // If a set query, will have one SetQueryDisplayNode SetQueryDisplayNode setQueryDisplayNode = (SetQueryDisplayNode)sqlDisplayNode; return setQueryDisplayNode.getQueryIndex(index); } return -1; } /** * Returns the QueryDisplayNode, given a queryIndex, or null if the index is invalid. * @param the query index. * @return the QueryDisplayNode for the provided queryIndex. */ public QueryDisplayNode getQueryDisplayNode(int queryIndex) { QueryDisplayNode queryDisplayNode = null; if(sqlDisplayNode!=null && sqlDisplayNode instanceof QueryDisplayNode && queryIndex==0) { // If a query, will have one QueryDisplayNode queryDisplayNode = (QueryDisplayNode)sqlDisplayNode; } else if(sqlDisplayNode!=null && sqlDisplayNode instanceof SetQueryDisplayNode) { // If a set query, will have one SetQueryDisplayNode SetQueryDisplayNode setQueryDisplayNode = (SetQueryDisplayNode)sqlDisplayNode; // Get the queryIndex Query from the SetQuery queryDisplayNode = setQueryDisplayNode.getQueryDisplayNode(queryIndex); } return queryDisplayNode; } /** * Returns a list of DisplayNodes (potentially 2) at a given cursor index */ public List getDisplayNodesAtIndex(int index) { List allNodes = getDisplayNodeList(); return DisplayNodeUtils.getDisplayNodesAtIndex(allNodes,index); } /** * Returns the DisplayNode for the Select if there is one, null if not */ public SelectDisplayNode getSelectDisplayNode( ) { if(sqlDisplayNode!=null && sqlDisplayNode instanceof QueryDisplayNode) { // If a query, will have one QueryDisplayNode QueryDisplayNode queryDisplayNode = (QueryDisplayNode)sqlDisplayNode; return (SelectDisplayNode)queryDisplayNode.getClauseDisplayNode(SELECT); } return null; } /** * Returns the DisplayNode for the Select if there is one, null if not * The user supplies a queryIndex (for Union queries) to specify which query they want * the select for. For non-setQuery, only an index of 0 will return non-null value. */ public SelectDisplayNode getSelectDisplayNode(int queryIndex) { QueryDisplayNode queryDisplayNode = getQueryDisplayNode(queryIndex); if(queryDisplayNode!=null) { return (SelectDisplayNode)queryDisplayNode.getClauseDisplayNode(SELECT); } return null; } /** * Returns the DisplayNode for the From if there is one, null if not */ public FromDisplayNode getFromDisplayNode( ) { if(sqlDisplayNode!=null && sqlDisplayNode instanceof QueryDisplayNode) { // If a query, will have one QueryDisplayNode QueryDisplayNode queryDisplayNode = (QueryDisplayNode)sqlDisplayNode; return (FromDisplayNode)queryDisplayNode.getClauseDisplayNode(FROM); } return null; } /** * Returns the DisplayNode for the From if there is one, null if not * The user supplies a queryIndex (for Union queries) to specify which query they want * the select for. For non-setQuery, only an index of 0 will return non-null value. */ public FromDisplayNode getFromDisplayNode(int queryIndex) { QueryDisplayNode queryDisplayNode = getQueryDisplayNode(queryIndex); if(queryDisplayNode!=null) { return (FromDisplayNode)queryDisplayNode.getClauseDisplayNode(FROM); } return null; } /** * Returns the DisplayNode for the Where if there is one, null if not */ public WhereDisplayNode getWhereDisplayNode( ) { if(sqlDisplayNode!=null && sqlDisplayNode instanceof QueryDisplayNode) { // If a query, will have one QueryDisplayNode QueryDisplayNode queryDisplayNode = (QueryDisplayNode)sqlDisplayNode; return (WhereDisplayNode)queryDisplayNode.getClauseDisplayNode(WHERE); } return null; } /** * Returns the DisplayNode for the Where if there is one, null if not * The user supplies a queryIndex (for Union queries) to specify which query they want * the select for. For non-setQuery, only an index of 0 will return non-null value. */ public WhereDisplayNode getWhereDisplayNode(int queryIndex) { QueryDisplayNode queryDisplayNode = getQueryDisplayNode(queryIndex); if(queryDisplayNode!=null) { return (WhereDisplayNode)queryDisplayNode.getClauseDisplayNode(WHERE); } return null; } /** * Returns the list of DisplayNodes representing SelectElementSymbols */ public List getSelectSymbolDisplayNodes( ) { SelectDisplayNode selectNode = getSelectDisplayNode(); if(selectNode!=null) { return selectNode.getChildren(); } return Collections.EMPTY_LIST; } /** * Returns the list of DisplayNodes representing SelectElementSymbols * The user supplies a queryIndex (for Union queries) to specify which query they want * the select for. For non-setQuery, only an index of 0 will return non-null value. */ public List getSelectSymbolDisplayNodes(int queryIndex) { SelectDisplayNode selectNode = getSelectDisplayNode(queryIndex); if(selectNode!=null) { return selectNode.getChildren(); } return Collections.EMPTY_LIST; } /** * Determines whether the index is anywhere within a DisplayNode of the specified * type. * @param index the cursor index * @param nodeType the type of DisplayNode * @return true if the cursor index is within the specified type, false if not */ public boolean isIndexWithin(int index, int type) { boolean result = false; if(isParsable()) { result = DisplayNodeUtils.isIndexWithin(getDisplayNodeList(),index,type); } return result; } /** * Determines whether the specified type can be inserted at the specified index. * @param index the cursor index * @param type the type to insert * @return true if the specified type can be inserted, false if not */ public boolean isInsertAllowed(int index, int type) { boolean result = false; if(isParsable()) { result = DisplayNodeUtils.isInsertAllowed(getDisplayNodeList(),index,type); } return result; } /** * Determines whether the specified item is supported at the specified index * @return true if the item is supported, false if not */ public boolean isSupportedAtIndex(int itemType, int index) { // Handle Default Query case if(isDefaultQuery()) { String currentQuery = this.toDisplayString().toUpperCase(); int endIndexOfSelect = currentQuery.indexOf(SELECT_STR)+SELECT_STR.length(); int startIndexOfFrom = currentQuery.indexOf(FROM_STR); int endIndexOfFrom = currentQuery.indexOf(FROM_STR)+FROM_STR.length(); if(itemType==ELEMENT && index>=endIndexOfSelect && index<=startIndexOfFrom) { return true; } else if(itemType==GROUP && index>=endIndexOfFrom) { return true; } else { return false; } } if(!isParsable()) { return false; } DisplayNode node = getQueryClauseAtIndex(index); if(node!=null) { switch (itemType) { case ELEMENT: return node.supportsElement(); case GROUP: return node.supportsGroup(); case EXPRESSION: return node.supportsExpression(); case CRITERIA: return node.supportsCriteria(); default: break; } } return false; } /** * Method to insert a group symbol string at the specified index. If the clause * that the index is in will not accept a group, nothing is done. * @param groupName the new group name to insert * @param index the index location to insert the group */ public void insertGroup(String groupName,int index) { // Handle Default Query if(isDefaultQuery()) { setText(DEFAULT_QUERY+groupName); return; } // If index is at invalid location, return without doing anything if(!isSupportedAtIndex(GROUP,index)) { return; } DisplayNode clauseNode = getQueryClauseAtIndex(index); // Before inserting group into From Clause, check for duplicates if(clauseNode instanceof FromDisplayNode) { if( !containsGroup((FromDisplayNode)clauseNode, groupName) ) { int nodeIndex = getDisplayNodeInsertIndex(index); insertSymbolNameAtNodeIndex(groupName,nodeIndex); // check to make sure it's still resolvable } } else { int nodeIndex = getDisplayNodeInsertIndex(index); insertSymbolNameAtNodeIndex(groupName,nodeIndex); } } /** * Method to insert an element symbol string at the specified index. If the clause * that the index is in will not accept an element, nothing is done. * @param elementName the new element name to insert * @param index the index location to insert the element */ public void insertElement(String elementName,String parentName,int index) { // Handle Default Query if(isDefaultQuery()) { setText(SELECT_STR+SPACE+elementName+SPACE+FROM_STR+SPACE+parentName); return; } // If index is at invalid location, return without doing anything if(!isSupportedAtIndex(ELEMENT,index)) { return; } boolean insertAtEndOfSelect = false; // If the query is a Select *, expand it first if(isSelectStar()) { // Get the starting index of the * int starIndex = 0; Iterator iter = getSelectSymbolDisplayNodes().iterator(); while(iter.hasNext()) { DisplayNode node = (DisplayNode)iter.next(); if(node.getLanguageObject() instanceof ISymbol) { ILanguageObject langObj = node.getLanguageObject(); if(langObj instanceof IMultipleElementSymbol) { starIndex = node.getStartIndex(); break; } } } // If cursor index is after the *, set flag to insert element // at end of Select clause if(index>starIndex) { insertAtEndOfSelect = true; } // Expand the select clause expandSelect(); } // Convert the cursor index to a DisplayNode index int nodeIndex = getDisplayNodeInsertIndex(index); // Insert the elements Group at the end of the FROM insertGroupAtEndOfFrom(parentName); // Reset insertion index if inserting at end of SELECT if(insertAtEndOfSelect) { index = getSelectDisplayNode().getEndIndex(); nodeIndex = getDisplayNodeInsertIndex(index); } insertSymbolNameAtNodeIndex(elementName,nodeIndex); } /** * Method to insert a list of element symbols at the specified index. If the clause * that the index is in will not accept an element, nothing is done. * @param elementNames the list of new element names to insert * @param parentNames the list of corresponding parentNames for each element * @param index the index location to insert the element */ public void insertElements(List elementNames,List parentNames,int index) { //---------------------------------------------------------------- // Handle Default Query //---------------------------------------------------------------- if(isDefaultQuery()) { StringBuffer elementSB = new StringBuffer(); int nNames = elementNames.size(); for(int i=0; i<nNames; i++) { if(i==0) { elementSB.append((String)elementNames.get(i)); } else { elementSB.append(COMMA+SPACE+(String)elementNames.get(i)); } } // Eliminate duplicate parents List uniqueGroups = new ArrayList(); Iterator iter = parentNames.iterator(); while(iter.hasNext()) { String grp = (String)iter.next(); if( !uniqueGroups.contains(grp) ) { uniqueGroups.add(grp); } } StringBuffer groupSB = new StringBuffer(); int nGrps = uniqueGroups.size(); for(int i=0; i<nGrps; i++) { if(i==0) { groupSB.append((String)uniqueGroups.get(i)); } else { groupSB.append(COMMA+SPACE+(String)uniqueGroups.get(i)); } } setText("SELECT "+elementSB.toString()+" FROM "+groupSB.toString()); //$NON-NLS-1$ //$NON-NLS-2$ return; } //---------------------------------------------------------------- // Iterate backwards will insert them in the right order //---------------------------------------------------------------- int nElems = elementNames.size(); int nParents = parentNames.size(); if(nParents!=nElems || nElems==0) { return; } for(int i=nElems-1; i>=0; i--) { insertElement( (String)elementNames.get(i), (String)parentNames.get(i), index ); } } /** * Returns the String representation for this QueryDisplayComponent. * @return the SQL string for this QueryDisplayComponent. */ public String toDisplayString( ) { if (this.sqlText != null) { return sqlText; } return BLANK; } /** * Returns the String representation for this QueryDisplayComponent. * @return the SQL string for this QueryDisplayComponent. */ @Override public String toString( ) { return toDisplayString(); } /** * Get the DisplayNode index for inserting the next node, given a cursor index. * If the cursor index is between two DisplayNodes, the index of the second is used. * @param cursorIndex the supplied cursor index. * @return the corresponding DisplayNode index. */ private int getDisplayNodeInsertIndex(int cursorIndex) { List allNodes = getDisplayNodeList(); // Iterate thru all Display Nodes for(int i=0; i<allNodes.size(); i++) { DisplayNode node = (DisplayNode)allNodes.get(i); // Check whether index is in current node if(node.isAnywhereWithin(cursorIndex)) { return i+1; } } return -1; } /** * Test whether the supplied query can be expanded. The Query select must contain * a MultiElementSymbol, and must have at least one projected symbol. * @param query the query language object to test. * @return true if the query can be expanded, false if not. */ private boolean canExpand(IQuery query) { boolean canExpand = false; if(query!=null) { ISelect select = query.getSelect(); boolean hasMultiSymbol = false; // Test whether the SELECT has any multi-symbols. if(select!=null) { List syms = select.getSymbols(); for(int i=0; i<syms.size(); i++) { if(syms.get(i) instanceof IMultipleElementSymbol) { hasMultiSymbol = true; break; } } } // If the SELECT has at least one multi-symbol, test the projected symbols. if(hasMultiSymbol) { List<IExpression> symbols = CommandHelper.getProjectedSymbols(query); if(symbols.size()>0) { canExpand = true; } } } return canExpand; } /** * Replaces the specified query index range with the supplied string. * @param startIndex the starting index for the string replace. * @param endIndex the ending index for the string replace. * @param str the string to replace the index range with */ private void replace(int startIndex, int endIndex, String str) { StringBuffer sb = new StringBuffer(this.toDisplayString()); sb.replace(startIndex, endIndex, str); setText(sb.toString()); return; } /** * Replaces the select string for the current query. If the current query is not a * valid QUERY, no action is taken. * @param selectStr the new SELECT string for the current query. */ private void replaceSelect(String selectStr) { if(sqlDisplayNode!=null && sqlDisplayNode instanceof QueryDisplayNode) { StringBuffer sb = new StringBuffer(BLANK); // If a query, will have one QueryDisplayNode QueryDisplayNode queryDisplayNode = (QueryDisplayNode)sqlDisplayNode; // Iterate through the query clauses Iterator iter = queryDisplayNode.getChildren().iterator(); // Rebuild the sql string, replacing the Select while( iter.hasNext() ) { DisplayNode clauseNode = (DisplayNode)iter.next(); if(clauseNode.getLanguageObject() instanceof ISelect) { sb.append(selectStr); } else { sb.append(clauseNode.toString()); } } setText( sb.toString() ); } return; } /** * Method to insert a group symbol string at the end of the FROM clause (if there * is a FROM clause). * @param groupName the new group name to insert */ private void insertGroupAtEndOfFrom(String groupName) { FromDisplayNode fromNode = getFromDisplayNode(); if(fromNode!=null) { int fromEndIndex = fromNode.getEndIndex(); insertGroup(groupName,fromEndIndex-1); } } /** * Method to determine whether a From clause already contains the specified * group symbol name. * @param fromClause the From clause display node. * @param groupName the group Name to test for. */ private boolean containsGroup(FromDisplayNode fromClause, String groupName) { if(fromClause!=null) { IFrom from = (IFrom)fromClause.getLanguageObject(); IQueryService queryService = ModelerCore.getTeiidQueryService(); IQueryFactory factory = queryService.createQueryFactory(); return from.getGroups().contains(factory.createGroupSymbol(groupName)); } return false; } /** * Method to insert a group or element symbol string into the component at * the specified index. This will get the clause and examine where the index * location is relative to other symbols. It will insert the new symbol name * into the clause at the appropriate location. * @param symbolName the new symbol name to insert * @param index the index location to insert the string */ private void insertSymbolName(String symbolName,int index) { // Get the Clause at the index DisplayNode clauseNode = getQueryClauseAtIndex(index); //------------------------------------------------------------ // Get the DisplayNode at the Index //------------------------------------------------------------ List displayNodes = getDisplayNodesAtIndex(index); int nNodes = displayNodes.size(); DisplayNode displayNode = null; if(nNodes==2) { // If there are two nodes, take the 1st node, or second if a symbol displayNode = (DisplayNode)displayNodes.get(0); if( displayNode.getLanguageObject() instanceof ISymbol || displayNode.getLanguageObject() instanceof IUnaryFromClause) { displayNode = (DisplayNode)displayNodes.get(1); } } else if(nNodes==1) { displayNode = (DisplayNode)displayNodes.get(0); } else { return; } //------------------------------------------------------------ // Clause has no Symbols or Expressions //------------------------------------------------------------ if( clauseNode != null && !DisplayNodeUtils.hasSymbol(clauseNode) && !DisplayNodeUtils.hasExpression(clauseNode) ) { insertString(COMMA+symbolName+SPACE,displayNode.getEndIndex()+1); //------------------------------------------------------------ // Index is within an Expression //------------------------------------------------------------ } else if ( displayNode.isInExpression() ) { DisplayNode expressionNode = DisplayNodeUtils.getExpressionForNode(displayNode); int startIndex = expressionNode.getStartIndex(); if(index==startIndex) { insertString(SPACE+symbolName+COMMA,expressionNode.getStartIndex()); } else { insertString(COMMA+symbolName+SPACE,expressionNode.getEndIndex()+1); } //------------------------------------------------------------ // Index is within a Symbol //------------------------------------------------------------ } else if (displayNode.getLanguageObject() instanceof ISymbol || displayNode.getLanguageObject() instanceof IUnaryFromClause) { int startIndex = displayNode.getStartIndex(); if(index==startIndex) { insertString(SPACE+symbolName+COMMA,displayNode.getStartIndex()); } else { insertString(COMMA+symbolName+SPACE,displayNode.getEndIndex()+1); } //------------------------------------------------------------ // Index is not within a Symbol or Expression //------------------------------------------------------------ } else if( clauseNode != null ) { int startOfNextSymbol = DisplayNodeUtils.getStartIndexOfNextSymbol(clauseNode,index); int endOfPrevSymbol = DisplayNodeUtils.getEndIndexOfPreviousSymbol(clauseNode,index); int startOfNextExpr = DisplayNodeUtils.getStartIndexOfNextExpression(clauseNode,index); int endOfPrevExpr = DisplayNodeUtils.getEndIndexOfPreviousExpression(clauseNode,index); int startOfNext = getSmallestNonNegative(startOfNextSymbol,startOfNextExpr); int endOfPrev = getLargestNonNegative(endOfPrevSymbol,endOfPrevExpr); // If theres a symbol or expression before and after it if(startOfNext!=-1 && endOfPrev!=-1) { insertString(SPACE+symbolName+COMMA,startOfNext); // If theres just a symbol or expression after it } else if(startOfNext!=-1) { insertString(SPACE+symbolName+COMMA,startOfNext); // If theres just a symbol or expression before it } else if(endOfPrev!=-1) { insertString(COMMA+symbolName+SPACE,endOfPrev); } } } /** * Get the smallest non-negative integer of the two supplied. Returns -1 if * both are negative. */ private int getSmallestNonNegative(int intOne, int intTwo) { // Both Negative, return -1 if(intOne<0 && intTwo<0) { return -1; } // At least one non-Negative, return smallest non-zero. int minimum = Math.min(intOne,intTwo); if(minimum<0) { return Math.max(intOne,intTwo); } return minimum; } /** * Get the largest non-negative integer of the two supplied. Returns -1 if both * are negative. */ private int getLargestNonNegative(int intOne, int intTwo) { // Both Negative, return -1 if(intOne<0 && intTwo<0) { return -1; // At least one non-Negative, return largest. } return Math.max(intOne,intTwo); } /** * Method to insert a group or element symbol string into the component at * the specified NODE index. This will get the clause and examine where the index * location is relative to other symbols. It will insert the new symbol name * into the clause at the appropriate location. * @param symbolName the new symbol name to insert * @param index the Node index location to insert the string */ private void insertSymbolNameAtNodeIndex(String symbolName,int nodeIndex) { // Get the text index of the node List allNodes = getDisplayNodeList(); if(nodeIndex<0 || nodeIndex>allNodes.size()) { return; } int index = 0; if(nodeIndex==allNodes.size()) { DisplayNode node = (DisplayNode)allNodes.get(nodeIndex-1); index = node.getEndIndex()-1; } else { DisplayNode node = (DisplayNode)allNodes.get(nodeIndex); index = node.getStartIndex(); } insertSymbolName(symbolName,index); } /** * Insert a string into the query Editor Panel at the specified index * @param str the string to insert * @param index the index location to insert the string */ private void insertString(String str, int index) { StringBuffer currentSQL = new StringBuffer(this.toDisplayString()); currentSQL.insert(index,str); setText(currentSQL.toString()); } /** * Method the SqlEditorPanel or others can call to get the actual index of a visible cursor index value. Hidden/invisible nodes * may mask the actual index. All the methods in this class assume ALL NODES ARE VISIBLE. * @param index * @return * @since 5.0 */ public int getCorrectedIndex(int visibleCursorIndex) { int theIndex = visibleCursorIndex; if (this.sqlDisplayNode != null) { for (Iterator iter = this.sqlDisplayNode.getDisplayNodeList().iterator(); iter.hasNext();) { DisplayNode node = (DisplayNode)iter.next(); if (node.isVisible()) { theIndex = visibleCursorIndex + node.getStartIndex(); break; } } // for } return theIndex; } public DisplayNode getFirstVisibleNode() { for (Iterator iter = this.sqlDisplayNode.getDisplayNodeList().iterator(); iter.hasNext();) { DisplayNode node = (DisplayNode)iter.next(); if (node.isVisible()) { return node; } } // for return null; } public EObject getMappingRoot() { if( this.queryValidator != null ) { return this.queryValidator.getTransformationRoot(); } return null; } public int getQueryType() { return this.queryType; } }