/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.metamodel.query;
import java.util.List;
import org.apache.metamodel.schema.Column;
import org.apache.metamodel.schema.Relationship;
import org.apache.metamodel.schema.Table;
import org.apache.metamodel.util.BaseObject;
/**
* Represents a FROM item. FROM items can take different forms:
* <ul>
* <li>table FROMs (eg. "FROM products p")</li>
* <lI>join FROMs with an ON clause (eg. "FROM products p INNER JOIN orders o ON
* p.id = o.product_id")</li>
* <li>subquery FROMs (eg. "FROM (SELECT * FROM products) p")</li>
* <li>expression FROM (any string based from item)</li>
* </ul>
*
* @see FromClause
*/
public class FromItem extends BaseObject implements QueryItem, Cloneable {
private static final long serialVersionUID = -6559220014058975193L;
private Table _table;
private String _alias;
private Query _subQuery;
private JoinType _join;
private FromItem _leftSide;
private FromItem _rightSide;
private SelectItem[] _leftOn;
private SelectItem[] _rightOn;
private Query _query;
private String _expression;
/**
* Private constructor, used for cloning
*/
private FromItem() {
}
/**
* Constructor for table FROM clauses
*/
public FromItem(Table table) {
_table = table;
}
/**
* Constructor for sub-query FROM clauses
*
* @param subQuery
* the subquery to use
*/
public FromItem(Query subQuery) {
_subQuery = subQuery;
}
/**
* Constructor for join FROM clauses that join two tables using their
* relationship. The primary table of the relationship will be the left side
* of the join and the foreign table of the relationship will be the right
* side of the join.
*
* @param join
* the join type to use
* @param relationship
* the relationship to use for joining the tables
*/
public FromItem(JoinType join, Relationship relationship) {
_join = join;
_leftSide = new FromItem(relationship.getPrimaryTable());
Column[] columns = relationship.getPrimaryColumns();
_leftOn = new SelectItem[columns.length];
for (int i = 0; i < columns.length; i++) {
_leftOn[i] = new SelectItem(columns[i]);
}
_rightSide = new FromItem(relationship.getForeignTable());
columns = relationship.getForeignColumns();
_rightOn = new SelectItem[columns.length];
for (int i = 0; i < columns.length; i++) {
_rightOn[i] = new SelectItem(columns[i]);
}
}
/**
* Constructor for advanced join types with custom relationships
*
* @param join
* the join type to use
* @param leftSide
* the left side of the join
* @param rightSide
* the right side of the join
* @param leftOn
* what left-side select items to use for the ON clause
* @param rightOn
* what right-side select items to use for the ON clause
*/
public FromItem(JoinType join, FromItem leftSide, FromItem rightSide, SelectItem[] leftOn, SelectItem[] rightOn) {
_join = join;
_leftSide = leftSide;
_rightSide = rightSide;
_leftOn = leftOn;
_rightOn = rightOn;
}
/**
* Creates a single unvalidated from item based on a expression. Expression
* based from items are typically NOT datastore-neutral but are available
* for special "hacking" needs.
*
* Expression based from items can only be used for JDBC based datastores
* since they are translated directly into SQL.
*
* @param expression
* An expression to use for the from item, for example "MYTABLE".
*/
public FromItem(String expression) {
if (expression == null) {
throw new IllegalArgumentException("Expression cannot be null");
}
_expression = expression;
}
public String getAlias() {
return _alias;
}
public String getSameQueryAlias() {
if (_alias != null) {
return _alias;
}
if (_table != null) {
return _table.getQuotedName();
}
return null;
}
public FromItem setAlias(String alias) {
_alias = alias;
return this;
}
public Table getTable() {
return _table;
}
public Query getSubQuery() {
return _subQuery;
}
public JoinType getJoin() {
return _join;
}
public FromItem getLeftSide() {
return _leftSide;
}
public FromItem getRightSide() {
return _rightSide;
}
public SelectItem[] getLeftOn() {
return _leftOn;
}
public SelectItem[] getRightOn() {
return _rightOn;
}
public String getExpression() {
return _expression;
}
@Override
public String toSql() {
return toSql(false);
}
@Override
public String toSql(boolean includeSchemaInColumnPaths) {
final String stringNoAlias = toStringNoAlias(includeSchemaInColumnPaths);
final StringBuilder sb = new StringBuilder(stringNoAlias);
if (_join != null && _alias != null) {
sb.insert(0, '(');
sb.append(')');
}
if (_alias != null) {
sb.append(' ');
sb.append(_alias);
}
return sb.toString();
}
public String toStringNoAlias() {
return toStringNoAlias(false);
}
public String toStringNoAlias(boolean includeSchemaInColumnPaths) {
if (_expression != null) {
return _expression;
}
StringBuilder sb = new StringBuilder();
if (_table != null) {
if (_table.getSchema() != null && _table.getSchema().getName() != null) {
sb.append(_table.getSchema().getName());
sb.append('.');
}
sb.append(_table.getQuotedName());
} else if (_subQuery != null) {
sb.append('(');
sb.append(_subQuery.toSql(includeSchemaInColumnPaths));
sb.append(')');
} else if (_join != null) {
String leftSideAlias = _leftSide.getSameQueryAlias();
String rightSideAlias = _rightSide.getSameQueryAlias();
sb.append(_leftSide.toSql());
sb.append(' ');
sb.append(_join);
sb.append(" JOIN ");
sb.append(_rightSide.toSql());
for (int i = 0; i < _leftOn.length; i++) {
if (i == 0) {
sb.append(" ON ");
} else {
sb.append(" AND ");
}
SelectItem primary = _leftOn[i];
appendJoinOnItem(sb, leftSideAlias, primary);
sb.append(" = ");
SelectItem foreign = _rightOn[i];
appendJoinOnItem(sb, rightSideAlias, foreign);
}
}
return sb.toString();
}
private void appendJoinOnItem(StringBuilder sb, String sideAlias, SelectItem onItem) {
final FromItem fromItem = onItem.getFromItem();
if (fromItem != null && fromItem.getSubQuery() != null && fromItem.getAlias() != null) {
// there's a corner case scenario where an ON item references a
// subquery being joined. In that case the getSuperQueryAlias()
// method will include the subquery alias.
final String superQueryAlias = onItem.getSuperQueryAlias();
sb.append(superQueryAlias);
return;
}
if(_join != null && _leftSide.getJoin() != null) {
sb.append(onItem.toSql());
return;
}
if (sideAlias != null) {
sb.append(sideAlias);
sb.append('.');
}
final String superQueryAlias = onItem.getSuperQueryAlias();
sb.append(superQueryAlias);
}
/**
* Gets the alias of a table, if it is registered (and visible, ie. not part
* of a sub-query) in the FromItem
*
* @param table
* the table to get the alias for
* @return the alias or null if none is found
*/
public String getAlias(Table table) {
String result = null;
if (table != null) {
// Search recursively through left and right side, unless they
// are sub-query FromItems
if (table.equals(_table)) {
result = _alias;
} else if (_join != null) {
result = _rightSide.getAlias(table);
if (result == null) {
result = _leftSide.getAlias(table);
}
}
}
return result;
}
public Query getQuery() {
return _query;
}
public QueryItem setQuery(Query query) {
_query = query;
return this;
}
@Override
protected FromItem clone() {
FromItem f = new FromItem();
f._alias = _alias;
f._join = _join;
f._table = _table;
f._expression = _expression;
if (_subQuery != null) {
f._subQuery = _subQuery.clone();
}
if (_leftOn != null && _leftSide != null && _rightOn != null && _rightSide != null) {
f._leftSide = _leftSide.clone();
f._leftOn = _leftOn.clone();
f._rightSide = _rightSide.clone();
f._rightOn = _rightOn.clone();
}
return f;
}
@Override
protected void decorateIdentity(List<Object> identifiers) {
identifiers.add(_table);
identifiers.add(_alias);
identifiers.add(_subQuery);
identifiers.add(_join);
identifiers.add(_leftSide);
identifiers.add(_rightSide);
identifiers.add(_leftOn);
identifiers.add(_rightOn);
identifiers.add(_expression);
}
@Override
public String toString() {
return toSql();
}
}