/*
* Copyright (c) 2008, SQL Power Group Inc.
*
* This file is part of Wabit.
*
* Wabit is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Wabit is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package ca.sqlpower.query;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Map.Entry;
import javax.annotation.concurrent.GuardedBy;
import org.apache.log4j.Logger;
import ca.sqlpower.graph.DepthFirstSearch;
import ca.sqlpower.graph.GraphModel;
import ca.sqlpower.sql.JDBCDataSource;
import ca.sqlpower.sql.SPDataSource;
import ca.sqlpower.sqlobject.SQLColumn;
import ca.sqlpower.sqlobject.SQLDatabase;
import ca.sqlpower.sqlobject.SQLDatabaseMapping;
import ca.sqlpower.sqlobject.SQLObjectException;
import ca.sqlpower.sqlobject.SQLTable;
import ca.sqlpower.util.TransactionEvent;
/**
* This class will cache all of the parts of a select
* statement and also listen to everything that could
* change the select statement.
*/
public class QueryImpl implements Query {
private static final Logger logger = Logger.getLogger(QueryImpl.class);
/**
* If the row limit changes causing the result set cache to become empty
* a change event will fire with this property.
*/
public static final String ROW_LIMIT = "rowLimit";
/**
* A property name that is thrown in PropertyChangeListeners when part of
* the query has changed. This is a generic default change to a query
* rather than a specific query change.
*/
public static final String PROPERTY_QUERY = "query";
/**
* This is the property name for grouping enabled.
*/
public static final String GROUPING_ENABLED = "groupingEnabled";
/**
* This is the property name for the global where clause text.
*/
public static final String GLOBAL_WHERE_CLAUSE = "globalWhereClause";
/**
* The arguments that can be added to a column in the
* order by clause.
*/
public enum OrderByArgument {
ASC,
DESC,
NONE
}
/**
* This graph represents the tables in the SQL statement. Each table in
* the statement is a vertex in the graph. Each join is an edge in the
* graph coming from the left table and moving towards the right table.
* This class is package private for testing.
*/
class TableJoinGraph implements GraphModel<Container, SQLJoin> {
public Collection<Container> getAdjacentNodes(Container node) {
List<Container> adjacencyNodes = new ArrayList<Container>();
if (joinMapping.get(node) != null) {
for (SQLJoin join : joinMapping.get(node)) {
if (join.getLeftColumn().getContainer() == node) {
adjacencyNodes.add(join.getRightColumn().getContainer());
} else {
adjacencyNodes.add(join.getLeftColumn().getContainer());
}
}
}
return adjacencyNodes;
}
public Collection<SQLJoin> getEdges() {
List<SQLJoin> edgesList = new ArrayList<SQLJoin>();
for (List<SQLJoin> joinList : joinMapping.values()) {
for (SQLJoin join : joinList) {
edgesList.add(join);
}
}
return edgesList;
}
public Collection<SQLJoin> getInboundEdges(Container node) {
List<SQLJoin> inboundEdges = new ArrayList<SQLJoin>();
if (joinMapping.get(node) != null) {
for (SQLJoin join : joinMapping.get(node)) {
inboundEdges.add(join);
}
}
return inboundEdges;
}
public Collection<Container> getNodes() {
return fromTableList;
}
public Collection<SQLJoin> getOutboundEdges(Container node) {
List<SQLJoin> outboundEdges = new ArrayList<SQLJoin>();
if (joinMapping.get(node) != null) {
for (SQLJoin join : joinMapping.get(node)) {
outboundEdges.add(join);
}
}
return outboundEdges;
}
}
/**
* Tracks if there are groupings added to this select statement.
* This will affect when columns are added to the group by collections.
*/
private boolean groupingEnabled = false;
/**
* The list of tables that we are selecting from.
*/
private final List<Container> fromTableList;
/**
* This maps each table to a list of SQLJoin objects.
* These column pairs defines a join in the select statement.
*/
private final Map<Container, List<SQLJoin>> joinMapping;
/**
* This is the global where clause that is for all non-column-specific where
* entries.
*/
private String globalWhereClause;
/**
* This is the level of the zoom in the query.
*/
private int zoomLevel;
/**
* The number times {@link #startCompoundEdit()} has been called without
* {@link #endCompoundEdit()} being called. When this level goes from 1 to
* 0 an end compound edit event will occur.
*/
private int compoundEditLevel = 0;
@GuardedBy("changeListeners")
private final List<QueryChangeListener> changeListeners = new ArrayList<QueryChangeListener>();
/**
* Listens for changes to the item and fires events to its listeners.
* <p>
* Now that the selection and order by list positions are stored in the item
* itself this listener is probably unnecessary.
*/
private PropertyChangeListener itemListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
fireItemPropertyChangeEvent(e);
}
};
/**
* This change listener will re-send the change event to listeners on this
* query. This will also keep the map of {@link Container}s to the joins on
* them in order.
*/
private PropertyChangeListener joinChangeListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
//XXX This if/else looks like leftovers from correcting listeners in Wabit and probably just needs to be deleted.
SQLJoin join = (SQLJoin) e.getSource();
if (e.getPropertyName().equals("leftColumnOuterJoin")) {
updateJoinsOnInnerOuterChange(join, (Boolean) e.getNewValue(), true);
} else if (e.getPropertyName().equals("rightColumnOuterJoin")) {
updateJoinsOnInnerOuterChange(join, (Boolean) e.getNewValue(), false);
}
fireJoinPropertyChangeEvent(e);
}
};
private final ContainerChildListener tableChildListener = new ContainerChildListener() {
public void containerChildRemoved(ContainerChildEvent e) {
removeItem(e.getChild());
}
public void containerChildAdded(ContainerChildEvent e) {
addItem(e.getChild());
}
};
/**
* This container holds the items that are considered constants in the SQL statement.
* This could include functions or other elements that don't belong in a table.
*/
private final Container constantsContainer;
/**
* This database instance is obtained from the session when the
* data source is called.
*/
private SQLDatabase database;
/**
* This is the text of the query if the user edited the text manually. This means
* that the parts of the query cache will not represent the new query text the user
* created. If this is null then the user did not change the query manually.
*/
private String userModifiedQuery = null;
private SQLDatabaseMapping dbMapping;
private String name;
/**
* This is the streaming row limit for the query. No more than this many
* rows will be shown in the streaming result set and if this limit is
* reached and new rows are added then the oldest rows in the result set
* will be removed.
*/
private int streamingRowLimit = 1000;
/**
* This is the row limit of a standard query.
*/
private int rowLimit = 1000;
/**
* Tracks if the data source should be used as a streaming query or as a regular
* query. Streaming queries are populated on their own thread.
*/
private boolean streaming = false;
/**
* A property change of this type is fired if the user defined
* text of the query is modified. Property changes to the objects
* maintained and monitored by this query will not contain this
* type.
*/
public static final String USER_MODIFIED_QUERY = "userModifiedQuery";
/**
* A property name that is thrown when the Table is removed.
*/
public static final String PROPERTY_TABLE_REMOVED = "PROPERTY_TABLE_REMOVED";
/**
* A unique identifier for this query.
*/
private String uuid;
/**
* Creates a Query implementation which will populate the constants table
* with some handy basic constants.
*
* @param dbMapping
* A mapping of {@link SPDataSource} objects that define a
* connection to {@link SQLDatabase}s that allow connecting to
* the data source and facilitates pooling of connections.
*/
public QueryImpl(SQLDatabaseMapping dbMapping) {
this(dbMapping, true);
}
/**
* Creates a Query implementation which can optionally populate the
* constants table with some handy basic constants.
*
* @param dbMapping
* A mapping of {@link SPDataSource} objects that define a
* connection to {@link SQLDatabase}s that allow connecting to
* the data source and facilitates pooling of connections.
* @param prepopulateConstants
* True if basic constants should be added to the query's
* constant table, false if the constants table should start
* empty.
*/
public QueryImpl(SQLDatabaseMapping dbMapping, boolean prepopulateConstants) {
this(dbMapping, prepopulateConstants, null);
}
/**
* Creates a Query implementation which can optionally populate the
* constants table with some handy basic constants.
*
* @param dbMapping
* A mapping of {@link SPDataSource} objects that define a
* connection to {@link SQLDatabase}s that allow connecting to
* the data source and facilitates pooling of connections.
* @param prepopulateConstants
* True if basic constants should be added to the query's
* constant table, false if the constants table should start
* empty.
* @param consantsContainer
* The constants container to use instead of creating a new one.
* If this is null a new one will be created.
*/
public QueryImpl(SQLDatabaseMapping dbMapping, boolean prepopulateConstants,
Container newConstantsContainer) {
this(dbMapping, prepopulateConstants, newConstantsContainer, null);
}
/**
* Creates a Query implementation which can set the data source and
* optionally populate the constants table with some handy basic constants.
*
* @param dbMapping
* A mapping of {@link SPDataSource} objects that define a
* connection to {@link SQLDatabase}s that allow connecting to
* the data source and facilitates pooling of connections.
* @param prepopulateConstants
* True if basic constants should be added to the query's
* constant table, false if the constants table should start
* empty.
* @param consantsContainer
* The constants container to use instead of creating a new one.
* If this is null a new one will be created.
* @param dataSource
* The data source that should be used in creating and executing
* queries. If this value is null, no data source will be used
* until the user defines one.
*/
public QueryImpl(SQLDatabaseMapping dbMapping, boolean prepopulateConstants,
Container newConstantsContainer, JDBCDataSource dataSource) {
this.dbMapping = dbMapping;
fromTableList = new ArrayList<Container>();
joinMapping = new LinkedHashMap<Container, List<SQLJoin>>();
if (newConstantsContainer != null) {
constantsContainer = newConstantsContainer;
} else {
constantsContainer = new ItemContainer("Constants");
}
constantsContainer.addChildListener(tableChildListener);
if (dataSource != null) {
setDataSourceWithoutSideEffects(dataSource);
}
if (prepopulateConstants) {
resetConstantsContainer();
}
}
/**
* This method will remove all of the current items in the constants
* container and the add in the default constants.
*/
private void resetConstantsContainer() {
constantsContainer.setPosition(new Point2D.Double(0, 0));
for (int i = constantsContainer.getItems().size() - 1; i >= 0; i--) {
constantsContainer.removeItem(i);
}
StringItem currentTime = new StringItem("current_time");
constantsContainer.addItem(currentTime);
StringItem currentDate = new StringItem("current_date");
constantsContainer.addItem(currentDate);
StringItem user = new StringItem("user");
constantsContainer.addItem(user);
StringItem countStar = new StringItem("count(*)");
constantsContainer.addItem(countStar);
}
public QueryImpl(QueryImpl copy, boolean connectListeners) {
this(copy, connectListeners, copy.getDatabase());
}
/**
* A copy constructor for the query cache. This will not
* hook up listeners.
*/
public QueryImpl(QueryImpl copy, boolean connectListeners, SQLDatabase database) {
fromTableList = new ArrayList<Container>();
joinMapping = new LinkedHashMap<Container, List<SQLJoin>>();
this.dbMapping = copy.dbMapping;
setName(copy.getName());
Map<Container, Container> oldToNewContainers = new HashMap<Container, Container>();
for (Container table : copy.getFromTableList()) {
final Container tableCopy = table.createCopy();
oldToNewContainers.put(table, tableCopy);
fromTableList.add(tableCopy);
}
constantsContainer = copy.getConstantsContainer().createCopy();
oldToNewContainers.put(copy.getConstantsContainer(), constantsContainer);
Set<SQLJoin> joinSet = new HashSet<SQLJoin>();
for (Map.Entry<Container, List<SQLJoin>> entry : copy.getJoinMapping().entrySet()) {
joinSet.addAll(entry.getValue());
}
Set<SQLJoin> newJoinSet = new HashSet<SQLJoin>();
for (SQLJoin oldJoin : joinSet) {
Container newLeftContainer = oldToNewContainers.get(oldJoin.getLeftColumn().getContainer());
Item newLeftItem = newLeftContainer.getItem(oldJoin.getLeftColumn().getItem());
Container newRightContainer = oldToNewContainers.get(oldJoin.getRightColumn().getContainer());
Item newRightItem = newRightContainer.getItem(oldJoin.getRightColumn().getItem());
SQLJoin newJoin = oldJoin.createCopy(newLeftItem, newRightItem);
newJoinSet.add(newJoin);
List<SQLJoin> newJoinList = joinMapping.get(newLeftContainer);
if (newJoinList == null) {
newJoinList = new ArrayList<SQLJoin>();
joinMapping.put(newLeftContainer, newJoinList);
}
newJoinList.add(newJoin);
newJoinList = joinMapping.get(newRightContainer);
if (newJoinList == null) {
newJoinList = new ArrayList<SQLJoin>();
joinMapping.put(newRightContainer, newJoinList);
}
newJoinList.add(newJoin);
newJoin.setParent(this);
}
globalWhereClause = copy.getGlobalWhereClause();
groupingEnabled = copy.isGroupingEnabled();
streaming = copy.isStreaming();
this.database = database;
userModifiedQuery = copy.getUserModifiedQuery();
if (connectListeners) {
for (Container table : fromTableList) {
table.addChildListener(getTableChildListener());
for (Item column : table.getItems()) {
column.addPropertyChangeListener(itemListener);
}
}
constantsContainer.addChildListener(getTableChildListener());
for (Item column : constantsContainer.getItems()) {
column.addPropertyChangeListener(itemListener);
}
for (SQLJoin join : newJoinSet) {
join.addJoinChangeListener(joinChangeListener);
}
}
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#getDbMapping()
*/
public SQLDatabaseMapping getDBMapping() {
return dbMapping;
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#setDBMapping(ca.sqlpower.sqlobject.SQLDatabaseMapping)
*/
public void setDBMapping(SQLDatabaseMapping dbMapping) {
this.dbMapping = dbMapping;
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#getDatabase()
*/
public SQLDatabase getDatabase() {
return database;
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#setGroupingEnabled(boolean)
*/
public void setGroupingEnabled(boolean enabled) {
logger.debug("Setting grouping enabled to " + enabled);
if (!groupingEnabled && enabled) {
startCompoundEdit("Defining the grouping function of string items to be count.");
for (Item item : getSelectedColumns()) {
if (item instanceof StringItem) {
item.setGroupBy(SQLGroupFunction.COUNT);
}
}
endCompoundEdit();
}
boolean oldGrouping = groupingEnabled;
groupingEnabled = enabled;
firePropertyChangeEvent(new PropertyChangeEvent(this, GROUPING_ENABLED, oldGrouping, enabled));
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#generateQuery()
*/
public String generateQuery() {
JDBCDataSource dataSource = null;
if (database != null) {
dataSource = database.getDataSource();
}
logger.debug("Data source is " + dataSource + " while generating the query.");
ConstantConverter converter = ConstantConverter.getConverter(dataSource);
if (userModifiedQuery != null) {
return userModifiedQuery;
}
String quoteString = "";
Connection con = null;
if (database != null) {
try {
con = database.getConnection();
quoteString = con.getMetaData().getIdentifierQuoteString();
} catch (SQLObjectException e) {
// Don't throw an exception for cases where we can't connect to the
// database but the user still wants to view or save the query. If
// this throws an exception going, to the SQL text tab will
// cause an exception.
} catch (SQLException e) {
// Don't throw an exception for cases where we can't connect to the
// database but the user still wants to view or save the query. If
// this throws an exception going, to the SQL text tab will
// cause an exception.
} finally {
if (con != null) {
try {
con.close();
} catch (SQLException e) {
logger.error(e);
}
}
}
}
if (getSelectedColumns().size() == 0) {
return "";
}
StringBuffer query = new StringBuffer();
query.append("SELECT");
boolean isFirstSelect = true;
for (Item col : getSelectedColumns()) {
if (isFirstSelect) {
query.append(" ");
isFirstSelect = false;
} else {
query.append(", ");
}
if (isColumnGrouped(col)) {
query.append(col.getGroupBy() + "(");
}
String alias = col.getContainer().getAlias();
if (alias != null && alias.length() > 0) {
query.append(quoteString + alias + quoteString + ".");
} else if (fromTableList.contains(col.getContainer())) {
query.append(quoteString + col.getContainer().getName() + quoteString + ".");
}
query.append(getColumnName(col, quoteString, converter));
if (isColumnGrouped(col)) {
query.append(")");
}
if (col.getAlias() != null && col.getAlias().trim().length() > 0) {
query.append(" AS " + quoteString + col.getAlias() + quoteString);
} else if (isGroupingEnabled() && col.getGroupBy() != null && col.getGroupBy() != SQLGroupFunction.GROUP_BY) {
query.append(" AS " + quoteString + col.getGroupBy() + "_" + col.getName() + quoteString);
}
}
if (!fromTableList.isEmpty()) {
query.append(" \nFROM");
}
boolean isFirstFrom = true;
DepthFirstSearch<Container, SQLJoin> dfs = new DepthFirstSearch<Container, SQLJoin>();
dfs.performSearch(new TableJoinGraph());
Container previousTable = null;
for (Container table : dfs.getFinishOrder()) {
String qualifiedName;
if (table.getContainedObject() instanceof SQLTable) {
qualifiedName = ((SQLTable)table.getContainedObject()).toQualifiedName(quoteString);
} else {
qualifiedName = table.getName();
}
String alias = table.getAlias();
if (alias == null || alias.length() <= 0) {
alias = table.getName();
}
alias = quoteString + alias + quoteString;
if (isFirstFrom) {
query.append(" " + qualifiedName + " " + alias);
isFirstFrom = false;
} else {
boolean joinFound = false;
if (previousTable != null && joinMapping.get(table) != null) {
for (SQLJoin join : joinMapping.get(table)) {
if (join.getLeftColumn().getContainer() == previousTable) {
joinFound = true;
if (join.isLeftColumnOuterJoin() && join.isRightColumnOuterJoin()) {
query.append(" \nFULL OUTER JOIN ");
} else if (join.isLeftColumnOuterJoin() && !join.isRightColumnOuterJoin()) {
query.append(" \nLEFT OUTER JOIN ");
} else if (!join.isLeftColumnOuterJoin() && join.isRightColumnOuterJoin()) {
query.append(" \nRIGHT OUTER JOIN ");
} else {
query.append(" \nINNER JOIN ");
}
break;
}
}
}
if (!joinFound) {
query.append(" \nINNER JOIN ");
}
query.append(qualifiedName + " " + alias + " \n ON ");
if (joinMapping.get(table) == null || joinMapping.get(table).isEmpty()) {
query.append("0 = 0");
} else {
boolean isFirstJoin = true;
for (SQLJoin join : joinMapping.get(table)) {
Item otherColumn;
if (join.getLeftColumn().getContainer() == table) {
otherColumn = join.getRightColumn();
} else {
otherColumn = join.getLeftColumn();
}
for (int i = 0; i < dfs.getFinishOrder().indexOf(table); i++) {
if (otherColumn.getContainer() == dfs.getFinishOrder().get(i)) {
if (isFirstJoin) {
isFirstJoin = false;
} else {
query.append(" \n AND ");
}
String leftAlias = join.getLeftColumn().getContainer().getAlias();
if (leftAlias == null || leftAlias.length() <= 0) {
leftAlias = join.getLeftColumn().getContainer().getName();
}
String rightAlias = join.getRightColumn().getContainer().getAlias();
if (rightAlias == null || rightAlias.length() <= 0) {
rightAlias = join.getRightColumn().getContainer().getName();
}
query.append(quoteString + leftAlias + quoteString + "." + getColumnName(join.getLeftColumn(), quoteString, converter) +
" " + join.getComparator() + " " +
quoteString + rightAlias + quoteString + "." + getColumnName(join.getRightColumn(), quoteString, converter));
}
}
}
if (isFirstJoin) {
query.append("0 = 0");
}
}
}
previousTable = table;
}
query.append(" ");
boolean isFirstWhere = true;
Map<Item, String> whereMapping = new HashMap<Item, String>();
for (Item item : constantsContainer.getItems()) {
if (item.getWhere() != null && item.getWhere().trim().length() > 0) {
whereMapping.put(item, item.getWhere());
}
}
for (Container container : fromTableList) {
for (Item item : container.getItems()) {
if (item.getWhere() != null && item.getWhere().trim().length() > 0) {
whereMapping.put(item, item.getWhere());
}
}
}
for (Map.Entry<Item, String> entry : whereMapping.entrySet()) {
if (entry.getValue().length() > 0) {
if (isFirstWhere) {
query.append(" \nWHERE ");
isFirstWhere = false;
} else {
query.append(" AND ");
}
String alias = entry.getKey().getContainer().getAlias();
if (alias != null && alias.length() > 0) {
query.append(quoteString + alias + quoteString + ".");
} else if (fromTableList.contains(entry.getKey().getContainer())) {
query.append(quoteString + entry.getKey().getContainer().getName() + quoteString + ".");
}
query.append(getColumnName(entry.getKey(), quoteString, converter) + " " + entry.getValue());
}
}
if ((globalWhereClause != null && globalWhereClause.length() > 0)) {
if (!isFirstWhere) {
query.append(" AND");
} else {
query.append(" \nWHERE ");
}
query.append(" " + globalWhereClause);
}
if (groupingEnabled) {
boolean isFirstGroupBy = true;
for (Item col : getSelectedColumns()) {
if (col.getGroupBy().equals(SQLGroupFunction.GROUP_BY) && !isStringItemAggregated(col)) {
if (isFirstGroupBy) {
query.append("\nGROUP BY ");
isFirstGroupBy = false;
} else {
query.append(", ");
}
String alias = col.getContainer().getAlias();
if (alias != null && alias.length() > 0) {
query.append(quoteString + alias + quoteString + ".");
} else if (fromTableList.contains(col.getContainer())) {
query.append(quoteString + col.getContainer().getName() + quoteString + ".");
}
query.append(getColumnName(col, quoteString, converter));
}
}
query.append(" ");
boolean isFirstHaving = true;
for (Item column : getSelectedColumns()) {
if (column.getHaving() != null && column.getHaving().trim().length() > 0) {
if (isFirstHaving) {
query.append("\nHAVING ");
isFirstHaving = false;
} else {
query.append(" AND ");
}
if (isColumnGrouped(column)) {
query.append(column.getGroupBy() + "(");
}
String alias = column.getContainer().getAlias();
if (alias != null && alias.length() > 0) {
query.append(quoteString + alias + quoteString + ".");
} else if (fromTableList.contains(column.getContainer())) {
query.append(quoteString + column.getContainer().getName() + quoteString + ".");
}
query.append(getColumnName(column, quoteString, converter));
if (isColumnGrouped(column)) {
query.append(")");
}
query.append(" ");
query.append(column.getHaving());
}
}
query.append(" ");
}
if (!getOrderByList().isEmpty()) {
boolean isFirstOrder = true;
for (Item col : getOrderByList()) {
if (isFirstOrder) {
query.append("\nORDER BY ");
isFirstOrder = false;
} else {
query.append(", ");
}
if (groupingEnabled && !col.getGroupBy().equals(SQLGroupFunction.GROUP_BY)) {
query.append(col.getGroupBy() + "(");
}
String alias = col.getContainer().getAlias();
if (alias != null && alias.length() > 0) {
query.append(quoteString + alias + quoteString + ".");
} else if (fromTableList.contains(col.getContainer())) {
query.append(quoteString + col.getContainer().getName() + quoteString + ".");
}
query.append(getColumnName(col, quoteString, converter));
if (groupingEnabled && !col.getGroupBy().equals(SQLGroupFunction.GROUP_BY)) {
query.append(")");
}
query.append(" ");
if (!col.getOrderBy().equals(OrderByArgument.NONE)) {
query.append(col.getOrderBy().toString() + " ");
}
}
}
logger.debug(" Query is : " + query.toString());
return query.toString();
}
/**
* Returns true if the column given is wrapped by a grouping function and we
* are actually grouping. The allowed grouping functions are stored in
* {@link SQLGroupFunction}. Returns false otherwise.
*/
private boolean isColumnGrouped(Item col) {
return groupingEnabled && !col.getGroupBy().equals(SQLGroupFunction.GROUP_BY)
&& !isStringItemAggregated(col);
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#containsCrossJoins()
*/
public boolean containsCrossJoins() {
DepthFirstSearch<Container, SQLJoin> dfs = new DepthFirstSearch<Container, SQLJoin>();
final TableJoinGraph graph = new TableJoinGraph();
dfs.performSearch(graph);
//If each container is connected to at least one of the containers
//that came before it in the finish order there will be no cross joins
List<Container> previousContainers = new ArrayList<Container>();
if (dfs.getFinishOrder().size() == 0) return false;
previousContainers.add(dfs.getFinishOrder().get(0));
for (int i = 1; i < dfs.getFinishOrder().size(); i++) {
Container container = dfs.getFinishOrder().get(i);
boolean connected = false;
List<SQLJoin> list = joinMapping.get(container);
if (list == null) return true;
for (SQLJoin join : list) {
Container leftContainer = join.getLeftColumn().getParent();
Container rightContainer = join.getRightColumn().getParent();
if ((leftContainer == container && previousContainers.contains(rightContainer))
|| (rightContainer == container && previousContainers.contains(leftContainer))) {
connected = true;
break;
}
}
if (!connected) {
return true;
}
previousContainers.add(container);
}
return false;
}
/**
* If the item passed in is a {@link StringItem} and it starts with an aggregator
* (like sum, count, avg) true will be returned. False will be returned otherwise.
*/
private boolean isStringItemAggregated(Item col) {
StringBuffer groupingRegex = new StringBuffer();
for (SQLGroupFunction function : SQLGroupFunction.values()) {
if (function != SQLGroupFunction.GROUP_BY) {
if (groupingRegex.length() == 0) {
groupingRegex.append("(");
} else {
groupingRegex.append("|");
}
groupingRegex.append(function.getGroupingName());
}
}
groupingRegex.append(").*");
boolean isStringItemAndAggregated = col instanceof StringItem
&& ((String) col.getItem()).toUpperCase().matches(
groupingRegex.toString().toUpperCase());
return isStringItemAndAggregated;
}
/**
* This is a helper method for {@link #generateQuery()} to properly return a
* column name. For items that represent {@link SQLColumn}s the name of the
* column needs to be quoted and will use the given quote string.
* Additionally, some columns that represent constants need to be converted
* to a different constant based on the database being queried. (For example
* current_time changes to current_timestamp in SQL Server.)
*
* @param item
* The item that represents a column in the query. The name of
* the item will be returned and may be possibly modified.
* @param quote
* The quote string to place before and after an item's name if
* it needs to be quoted.
* @param converter
* The database specific converter that will change some constant
* names to a valid constant in the database.
*/
private String getColumnName(Item item, String quote, ConstantConverter converter) {
if (item instanceof StringItem) return converter.getName(item);
if (item instanceof SQLObjectItem) return quote + item.getName() + quote;
throw new IllegalArgumentException("Unknown item type " + item.getClass() + " when trying to define a name for the item " + item.getName());
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#getSelectedColumns()
*/
public List<Item> getSelectedColumns() {
SortedMap<Integer, Item> sortedItems = getSelectedColumnsMap();
List<Item> selectedColumns = new ArrayList<Item>();
for (Map.Entry<Integer, Item> entry : sortedItems.entrySet()) {
selectedColumns.add(entry.getValue());
}
return Collections.unmodifiableList(selectedColumns);
}
/**
* Returns a map of selected columns where there can only be one item
* at each numeric position and each item selected is mapped to a single
* position.
*/
private SortedMap<Integer, Item> getSelectedColumnsMap() {
SortedMap<Integer, Item> sortedItems = new TreeMap<Integer, Item>();
List<Container> containers = new ArrayList<Container>(fromTableList);
containers.add(constantsContainer);
for (Container container : containers) {
for (Item item : container.getItems()) {
if (item.getSelected() != null) {
if (sortedItems.get(item.getSelected()) != null)
throw new IllegalStateException("The item " + item.getName() +
" has selected order " + item.getSelected() + " but the item " +
sortedItems.get(item.getSelected()).getName() + " already exists " +
"with the same sort order.");
sortedItems.put(item.getSelected(), item);
}
}
}
logger.debug("Selected columns are " + sortedItems);
return sortedItems;
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#getOrderByList()
*/
public List<Item> getOrderByList() {
SortedMap<Integer, Item> sortedItems = getOrderByMap();
List<Item> orderByColumns = new ArrayList<Item>();
for (Map.Entry<Integer, Item> entry : sortedItems.entrySet()) {
orderByColumns.add(entry.getValue());
}
return Collections.unmodifiableList(orderByColumns);
}
private SortedMap<Integer, Item> getOrderByMap() {
SortedMap<Integer, Item> sortedItems = new TreeMap<Integer, Item>();
List<Container> containers = new ArrayList<Container>(fromTableList);
containers.add(constantsContainer);
for (Container container : containers) {
for (Item item : container.getItems()) {
if (item.getSelected() != null && !item.getOrderBy().equals(OrderByArgument.NONE)
&& item.getOrderByOrdering() != null) {
if (sortedItems.get(item.getOrderByOrdering()) != null)
throw new IllegalStateException("The item " + item.getName() +
" has order by order " + item.getSelected() + " but the item " +
sortedItems.get(item.getSelected()).getName() + " already exists " +
"with the same order by order.");
sortedItems.put(item.getOrderByOrdering(), item);
}
}
}
return sortedItems;
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#moveSortedItemToEnd(ca.sqlpower.query.Item)
*/
public void moveOrderByItemToEnd(Item item) {
Integer itemPosition = item.getOrderByOrdering();
Integer maxPosition = -1;
for (Map.Entry<Integer, Item> entry : getOrderByMap().entrySet()) {
if (itemPosition != null && entry.getKey() > itemPosition) {
entry.getValue().setOrderByOrdering(entry.getValue().getOrderByOrdering() - 1);
}
maxPosition = entry.getValue().getOrderByOrdering();
}
item.setOrderByOrdering(maxPosition + 1);
}
public void orderColumn(Item item, OrderByArgument ordering) {
if (ordering.equals(OrderByArgument.NONE)) {
Integer itemPosition = item.getOrderByOrdering();
item.setOrderByOrdering(null);
for (Map.Entry<Integer, Item> entry : getOrderByMap().entrySet()) {
if (itemPosition != null && entry.getKey() > itemPosition) {
entry.getValue().setOrderByOrdering(entry.getValue().getOrderByOrdering() - 1);
}
}
} else {
moveOrderByItemToEnd(item);
}
item.setOrderBy(ordering);
}
/**
* Returns the index of the given item in the order by list. If the item
* does not exist in the order by list a negative number will be returned.
*
* @param item
* The item to get the index of in the order by list.
* @return The index of the item or a negative number if it is not in the
* list.
*/
public int indexOfOrderByItem(Item item) {
final List<Item> orderByList = getOrderByList();
for (int i = 0; i < orderByList.size(); i++) {
if (orderByList.get(i).equals(item)) {
return i;
}
}
return -1;
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#removeTable(ca.sqlpower.query.Container)
*/
public void removeTable(Container table) {
try {
startCompoundEdit("Removing table " + table.getName() + ", its columns and its joins.");
boolean removed = fromTableList.remove(table);
if (!removed) {
return;
}
table.removeChildListener(getTableChildListener());
for (Item col : table.getItems()) {
removeItem(col);
}
for (List<SQLJoin> joins : joinMapping.values()) {
for (int i = joins.size() - 1; i >= 0; i--) {
SQLJoin join = joins.get(i);
if (join.getLeftColumn().getParent().equals(table) ||
join.getRightColumn().getParent().equals(table)) {
removeJoin(join);
}
}
}
fireContainerRemoved(table);
} finally {
endCompoundEdit();
}
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#addTable(ca.sqlpower.query.Container)
*/
public void addTable(Container container) {
addTable(container, fromTableList.size());
}
public void addTable(Container container, int index) {
if (fromTableList.contains(container))
throw new IllegalArgumentException("The container " + container.getName() +
" already exists in the query " + getName());
try {
startCompoundEdit("Add Table " + container.getName());
fromTableList.add(index, container);
container.addChildListener(getTableChildListener());
for (Item col : container.getItems()) {
addItem(col);
}
fireContainerAdded(container);
} finally {
endCompoundEdit();
}
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#setGlobalWhereClause(java.lang.String)
*/
public void setGlobalWhereClause(String whereClause) {
String oldWhere = globalWhereClause;
globalWhereClause = whereClause;
firePropertyChangeEvent(new PropertyChangeEvent(this, GLOBAL_WHERE_CLAUSE, oldWhere, whereClause));
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#removeJoin(ca.sqlpower.query.SQLJoin)
*/
public void removeJoin(SQLJoin joinLine) {
joinLine.removeJoinChangeListener(joinChangeListener);
Item leftColumn = joinLine.getLeftColumn();
Item rightColumn = joinLine.getRightColumn();
List<SQLJoin> leftJoinList = joinMapping.get(leftColumn.getContainer());
for (SQLJoin join : leftJoinList) {
if (leftColumn == join.getLeftColumn() && rightColumn == join.getRightColumn()) {
leftJoinList.remove(join);
break;
}
}
List<SQLJoin> rightJoinList = joinMapping.get(rightColumn.getContainer());
for (SQLJoin join : rightJoinList) {
if (leftColumn == join.getLeftColumn() && rightColumn == join.getRightColumn()) {
rightJoinList.remove(join);
break;
}
}
fireJoinRemoved(joinLine);
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#addJoin(ca.sqlpower.query.SQLJoin)
*/
public void addJoin(SQLJoin join) {
if (getJoins().contains(join))
throw new IllegalArgumentException("The join " + join.getName() +
" already exists in the query " + getName());
join.addJoinChangeListener(joinChangeListener);
Item leftColumn = join.getLeftColumn();
Item rightColumn = join.getRightColumn();
Container leftContainer = leftColumn.getContainer();
Container rightContainer = rightColumn.getContainer();
if (joinMapping.get(leftContainer) == null) {
List<SQLJoin> joinList = new ArrayList<SQLJoin>();
joinList.add(join);
joinMapping.put(leftContainer, joinList);
} else {
if (joinMapping.get(leftContainer).size() > 0) {
SQLJoin prevJoin = joinMapping.get(leftContainer).get(0);
if (prevJoin.getLeftColumn().getContainer() == leftContainer) {
join.setLeftColumnOuterJoin(prevJoin.isLeftColumnOuterJoin());
} else if (prevJoin.getRightColumn().getContainer() == leftContainer) {
join.setLeftColumnOuterJoin(prevJoin.isRightColumnOuterJoin());
}
}
joinMapping.get(leftContainer).add(join);
}
if (joinMapping.get(rightContainer) == null) {
List<SQLJoin> joinList = new ArrayList<SQLJoin>();
joinList.add(join);
joinMapping.put(rightContainer, joinList);
} else {
if (joinMapping.get(rightContainer).size() > 0) {
SQLJoin prevJoin = joinMapping.get(rightContainer).get(0);
if (prevJoin.getLeftColumn().getContainer() == rightContainer) {
join.setRightColumnOuterJoin(prevJoin.isLeftColumnOuterJoin());
} else if (prevJoin.getRightColumn().getContainer() == rightContainer) {
join.setRightColumnOuterJoin(prevJoin.isRightColumnOuterJoin());
} else {
throw new IllegalStateException("A table contains a join that is not connected to any of its columns in the table.");
}
}
joinMapping.get(rightContainer).add(join);
}
join.setParent(this);
fireJoinAdded(join);
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#removeItem(ca.sqlpower.query.Item)
*/
public void removeItem(Item col) {
logger.debug("Item name is " + col.getName());
col.removePropertyChangeListener(itemListener);
fireItemRemoved(col);
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#addItem(ca.sqlpower.query.Item)
*/
public void addItem(Item col) {
col.addPropertyChangeListener(itemListener);
fireItemAdded(col);
}
public void moveOrderBy(Item item, int index) {
SortedMap<Integer,Item> orderByMap = getOrderByMap();
for (Map.Entry<Integer, Item> entry : orderByMap.entrySet()) {
if (entry.getKey() >= index) {
entry.getValue().setSelected(entry.getValue().getSelected() + 1);
}
}
item.setSelected(index);
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#moveItem(ca.sqlpower.query.Item, int)
*/
public void moveItem(Item movedColumn, int toIndex) {
int oldIndex = movedColumn.getSelected();
movedColumn.setSelected(null);
SortedMap<Integer,Item> selectedColumnsMap = getSelectedColumnsMap();
final Set<Entry<Integer, Item>> entrySet = selectedColumnsMap.entrySet();
if (toIndex > oldIndex) {
for (Map.Entry<Integer, Item> entry : entrySet) {
if (entry.getKey() <= toIndex && entry.getKey() > oldIndex) {
entry.getValue().setSelected(entry.getKey() - 1);
}
}
} else {
List<Item> selectionsToShiftForward = new ArrayList<Item>();
for (Map.Entry<Integer, Item> entry : entrySet) {
if (entry.getKey() >= toIndex && entry.getKey() < oldIndex) {
selectionsToShiftForward.add(entry.getValue());
}
}
for (int i = selectionsToShiftForward.size() - 1; i >= 0; i--) {
Item item = selectionsToShiftForward.get(i);
item.setSelected(item.getSelected() + 1);
}
}
movedColumn.setSelected(toIndex);
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#startCompoundEdit(java.lang.String)
*/
public void startCompoundEdit(String message) {
int currentEditLevel = compoundEditLevel;
compoundEditLevel++;
if (currentEditLevel == 0) {
fireCompoundEditStarted(message);
}
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#endCompoundEdit()
*/
public void endCompoundEdit() {
compoundEditLevel--;
if (compoundEditLevel == 0) {
fireCompoundEditEnded();
}
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#isGroupingEnabled()
*/
public boolean isGroupingEnabled() {
return groupingEnabled;
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#getFromTableList()
*/
public List<Container> getFromTableList() {
return Collections.unmodifiableList(fromTableList);
}
protected Map<Container, List<SQLJoin>> getJoinMapping() {
return Collections.unmodifiableMap(joinMapping);
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#getJoins()
*/
public Collection<SQLJoin> getJoins() {
Set<SQLJoin> joinSet = new HashSet<SQLJoin>();
for (List<SQLJoin> joins : joinMapping.values()) {
for (SQLJoin join : joins) {
joinSet.add(join);
}
}
return joinSet;
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#getGlobalWhereClause()
*/
public String getGlobalWhereClause() {
return globalWhereClause;
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#getConstantsContainer()
*/
public Container getConstantsContainer() {
return constantsContainer;
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#setDataSource(ca.sqlpower.sql.JDBCDataSource)
*/
public void setDataSource(JDBCDataSource dataSource) {
boolean dsSet = setDataSourceWithoutSideEffects(dataSource);
if (dsSet) {
reset();
}
}
public boolean setDataSourceWithoutSideEffects(JDBCDataSource dataSource) {
final SQLDatabase newDatabase = dbMapping.getDatabase(dataSource);
SQLDatabase old = database;
if (database != null && database == newDatabase) return false;
this.database = newDatabase;
firePropertyChangeEvent(new PropertyChangeEvent(this, "database", old, newDatabase));
return true;
}
public JDBCDataSource getDataSource() {
if (database == null) return null;
return database.getDataSource();
};
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#defineUserModifiedQuery(java.lang.String)
*/
public void setUserModifiedQuery(String query) {
String oldUserQuery = userModifiedQuery;
userModifiedQuery = query;
firePropertyChangeEvent(new PropertyChangeEvent(this, USER_MODIFIED_QUERY, oldUserQuery, query));
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#isScriptModified()
*/
public boolean isScriptModified() {
return userModifiedQuery != null;
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#removeUserModifications()
*/
public void removeUserModifications() {
logger.debug("Removing user modified query.");
setUserModifiedQuery(null);
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#setZoomLevel(int)
*/
public void setZoomLevel(int zoomLevel) {
int oldZoom = this.zoomLevel;
this.zoomLevel = zoomLevel;
firePropertyChangeEvent(new PropertyChangeEvent(this, "zoomLevel", oldZoom, zoomLevel));
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#getZoomLevel()
*/
public int getZoomLevel() {
return zoomLevel;
}
/**
* Used for constructing copies of the query cache.
*/
public String getUserModifiedQuery() {
return userModifiedQuery;
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#toString()
*/
@Override
public String toString() {
return getName();
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#getName()
*/
public String getName() {
return name;
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#setName(java.lang.String)
*/
public void setName(String name) {
String oldName = this.name;
this.name = name;
firePropertyChangeEvent(new PropertyChangeEvent(this, "name", oldName, name));
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#setRowLimit(int)
*/
public void setRowLimit(int rowLimit) {
int oldLimit = this.rowLimit;
this.rowLimit = rowLimit;
firePropertyChangeEvent(new PropertyChangeEvent(this, ROW_LIMIT, oldLimit, rowLimit));
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#getRowLimit()
*/
public int getRowLimit() {
return rowLimit;
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#setStreamingRowLimit(int)
*/
public void setStreamingRowLimit(int streamingRowLimit) {
int oldLimit = this.streamingRowLimit;
this.streamingRowLimit = streamingRowLimit;
firePropertyChangeEvent(new PropertyChangeEvent(this, "streamingRowLimit", oldLimit, streamingRowLimit));
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#getStreamingRowLimit()
*/
public int getStreamingRowLimit() {
return streamingRowLimit;
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#setStreaming(boolean)
*/
public void setStreaming(boolean streaming) {
boolean oldStreaming = this.streaming;
this.streaming = streaming;
firePropertyChangeEvent(new PropertyChangeEvent(this, "streaming", oldStreaming, streaming));
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#isStreaming()
*/
public boolean isStreaming() {
return streaming;
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#addQueryChangeListener(ca.sqlpower.query.QueryChangeListener)
*/
public void addQueryChangeListener(QueryChangeListener l) {
synchronized(changeListeners) {
changeListeners.add(l);
}
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#removeQueryChangeListener(ca.sqlpower.query.QueryChangeListener)
*/
public void removeQueryChangeListener(QueryChangeListener l) {
synchronized(changeListeners) {
changeListeners.remove(l);
}
}
/**
* This is package private as it is only used in testing and shouldn't be
* used anywhere else.
*/
ContainerChildListener getTableChildListener() {
return tableChildListener;
}
/**
* This is package private as it is only used in testing and shouldn't be
* used anywhere else.
*/
PropertyChangeListener getItemListener() {
return itemListener;
}
/**
* This is package private as it is only used in testing and shouldn't be
* used anywhere else.
*/
PropertyChangeListener getJoinChangeListener() {
return joinChangeListener;
}
/* (non-Javadoc)
* @see ca.sqlpower.query.Query#reset()
*/
public void reset() {
try {
startCompoundEdit("Resetting query");
for (int i = getFromTableList().size() - 1; i >= 0; i--) {
removeTable(getFromTableList().get(i));
}
resetConstantsContainer();
setGlobalWhereClause(null);
setGroupingEnabled(false);
setUserModifiedQuery(null);
setZoomLevel(0);
} finally {
endCompoundEdit();
}
}
public int indexOfSelectedItem(Item item) {
final List<Item> selectedColumns = getSelectedColumns();
for (int i = 0; i < selectedColumns.size(); i++) {
if (selectedColumns.get(i).equals(item)) {
return i;
}
}
return -1;
}
public void selectItem(Item item) {
if (item.getSelected() != null) return; //already selected
Integer max = -1;
if (getSelectedColumnsMap().keySet().size() > 0) {
max = Collections.max(getSelectedColumnsMap().keySet());
}
item.setSelected(max + 1);
}
public void unselectItem(Item item) {
if (item.getSelected() == null) return; //was not selected
Integer oldSelection = item.getSelected();
item.setSelected(null);
for (Map.Entry<Integer, Item> entry : getSelectedColumnsMap().entrySet()) {
if (entry.getKey().intValue() > oldSelection.intValue()) {
entry.getValue().setSelected(entry.getValue().getSelected() - 1);
}
}
}
//---------------------------- Protected methods to fire events -------------------
protected void fireJoinAdded(SQLJoin joinAdded) {
synchronized(changeListeners) {
for (int i = changeListeners.size() - 1; i >= 0; i--) {
changeListeners.get(i).joinAdded(new QueryChangeEvent(this, joinAdded));
}
}
}
protected void fireJoinRemoved(SQLJoin joinRemoved) {
synchronized(changeListeners) {
for (int i = changeListeners.size() - 1; i >= 0; i--) {
changeListeners.get(i).joinRemoved(new QueryChangeEvent(this, joinRemoved));
}
}
}
protected void fireJoinPropertyChangeEvent(PropertyChangeEvent e) {
synchronized(changeListeners) {
for (int i = changeListeners.size() - 1; i >= 0; i--) {
changeListeners.get(i).joinPropertyChangeEvent(e);
}
}
}
protected void fireItemPropertyChangeEvent(PropertyChangeEvent e) {
synchronized(changeListeners) {
for (int i = changeListeners.size() - 1; i >= 0; i--) {
changeListeners.get(i).itemPropertyChangeEvent(e);
}
}
}
protected void fireItemAdded(Item itemAdded) {
synchronized(changeListeners) {
for (int i = changeListeners.size() - 1; i >= 0; i--) {
changeListeners.get(i).itemAdded(new QueryChangeEvent(this, itemAdded));
}
}
}
protected void fireItemRemoved(Item itemRemoved) {
synchronized(changeListeners) {
for (int i = changeListeners.size() - 1; i >= 0; i--) {
changeListeners.get(i).itemRemoved(new QueryChangeEvent(this, itemRemoved));
}
}
}
protected void fireContainerAdded(Container containerAdded) {
synchronized(changeListeners) {
for (int i = changeListeners.size() - 1; i >= 0; i--) {
changeListeners.get(i).containerAdded(new QueryChangeEvent(this, containerAdded));
}
}
}
protected void fireContainerRemoved(Container containerRemoved) {
synchronized(changeListeners) {
for (int i = changeListeners.size() - 1; i >= 0; i--) {
changeListeners.get(i).containerRemoved(new QueryChangeEvent(this, containerRemoved));
}
}
}
protected void firePropertyChangeEvent(PropertyChangeEvent e) {
synchronized(changeListeners) {
for (int i = changeListeners.size() - 1; i >= 0; i--) {
changeListeners.get(i).propertyChangeEvent(e);
}
}
}
protected void fireCompoundEditStarted(String message) {
synchronized(changeListeners) {
for (int i = changeListeners.size() - 1; i >= 0; i--) {
changeListeners.get(i).compoundEditStarted(
TransactionEvent.createStartTransactionEvent(this, message));
}
}
}
protected void fireCompoundEditEnded() {
synchronized(changeListeners) {
for (int i = changeListeners.size() - 1; i >= 0; i--) {
changeListeners.get(i).compoundEditEnded(
TransactionEvent.createEndTransactionEvent(this));
}
}
}
public void setUUID(String uuid) {
this.uuid = uuid;
}
public String getUUID() {
return uuid;
}
//---------------------------- End of protected methods to fire events -------------------
/**
* When one join connected to a table is changed to be an inner or outer
* join all of the joins to this table must be set to the same inner or
* outer join state for the query to make sense.
*/
private void updateJoinsOnInnerOuterChange(SQLJoin joinChanged, boolean isOuterJoin, boolean isLeftColumn) {
if (isLeftColumn) {
logger.debug("Got left join changed.");
Container leftJoinContainer = joinChanged.getLeftColumn().getContainer();
for (SQLJoin join : joinMapping.get(leftJoinContainer)) {
if (join.getLeftColumn().getContainer() == leftJoinContainer) {
join.setLeftColumnOuterJoin(isOuterJoin);
} else {
join.setRightColumnOuterJoin(isOuterJoin);
}
}
} else {
logger.debug("Got right join changed.");
Container rightJoinContainer = joinChanged.getRightColumn().getContainer();
logger.debug("There are " + joinMapping.get(rightJoinContainer) + " joins on the table with the changed join.");
for (SQLJoin join : joinMapping.get(rightJoinContainer)) {
if (join.getLeftColumn().getContainer() == rightJoinContainer) {
logger.debug("Changing left side");
join.setLeftColumnOuterJoin(isOuterJoin);
} else {
logger.debug("Changing right side");
join.setRightColumnOuterJoin(isOuterJoin);
}
}
}
}
}