| /** |
| * 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.ColumnType; |
| import org.apache.metamodel.schema.Schema; |
| import org.apache.metamodel.schema.Table; |
| import org.apache.metamodel.util.BaseObject; |
| import org.apache.metamodel.util.EqualsBuilder; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Represents a SELECT item. SelectItems can take different forms: |
| * <ul> |
| * <li>column SELECTs (selects a column from a table)</li> |
| * <li>column function SELECTs (aggregates the values of a column)</li> |
| * <li>expression SELECTs (retrieves data based on an expression (only supported |
| * for JDBC datastores)</li> |
| * <li>expression function SELECTs (retrieves databased on a function and an |
| * expression, only COUNT(*) is supported for non-JDBC datastores))</li> |
| * <li>SELECTs from subqueries (works just like column selects, but in stead of |
| * pointing to a column, it retrieves data from the select item of a subquery) |
| * </li> |
| * </ul> |
| * |
| * @see SelectClause |
| */ |
| public class SelectItem extends BaseObject implements QueryItem, Cloneable { |
| |
| public static final String FUNCTION_APPROXIMATION_PREFIX = "APPROXIMATE "; |
| |
| private static final long serialVersionUID = 317475105509663973L; |
| private static final Logger logger = LoggerFactory.getLogger(SelectItem.class); |
| |
| // immutable fields (essense) |
| private final Column _column; |
| private final FunctionType _function; |
| private final String _expression; |
| private final SelectItem _subQuerySelectItem; |
| private final FromItem _fromItem; |
| |
| // mutable fields (tweaking) |
| private boolean _functionApproximationAllowed; |
| private Query _query; |
| private String _alias; |
| |
| /** |
| * All-arguments constructor |
| * |
| * @param column |
| * @param fromItem |
| * @param function |
| * @param expression |
| * @param subQuerySelectItem |
| * @param alias |
| * @param functionApproximationAllowed |
| */ |
| private SelectItem(Column column, FromItem fromItem, FunctionType function, String expression, |
| SelectItem subQuerySelectItem, String alias, boolean functionApproximationAllowed) { |
| super(); |
| _column = column; |
| _fromItem = fromItem; |
| _function = function; |
| _expression = expression; |
| _subQuerySelectItem = subQuerySelectItem; |
| _alias = alias; |
| _functionApproximationAllowed = functionApproximationAllowed; |
| } |
| |
| /** |
| * Generates a COUNT(*) select item |
| */ |
| public static SelectItem getCountAllItem() { |
| return new SelectItem(FunctionType.COUNT, "*", null); |
| } |
| |
| public static boolean isCountAllItem(SelectItem item) { |
| if (item != null && item.getFunction() != null && item.getFunction().toString().equals("COUNT") |
| && item.getExpression() == "*") { |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Creates a simple SelectItem that selects from a column |
| * |
| * @param column |
| */ |
| public SelectItem(Column column) { |
| this(null, column); |
| } |
| |
| /** |
| * Creates a SelectItem that uses a function on a column, for example |
| * SUM(price) or MAX(age) |
| * |
| * @param function |
| * @param column |
| */ |
| public SelectItem(FunctionType function, Column column) { |
| this(function, column, null); |
| } |
| |
| /** |
| * Creates a SelectItem that references a column from a particular |
| * {@link FromItem}, for example a.price or p.age |
| * |
| * @param column |
| * @param fromItem |
| */ |
| public SelectItem(Column column, FromItem fromItem) { |
| this(null, column, fromItem); |
| if (fromItem != null) { |
| Table fromItemTable = fromItem.getTable(); |
| if (fromItemTable != null) { |
| Table columnTable = column.getTable(); |
| if (columnTable != null && !columnTable.equals(fromItemTable)) { |
| throw new IllegalArgumentException("Column's table '" + columnTable.getName() |
| + "' is not equal to referenced table: " + fromItemTable); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Creates a SelectItem that uses a function on a column from a particular |
| * {@link FromItem}, for example SUM(a.price) or MAX(p.age) |
| * |
| * @param function |
| * @param column |
| * @param fromItem |
| */ |
| public SelectItem(FunctionType function, Column column, FromItem fromItem) { |
| this(column, fromItem, function, null, null, null, false); |
| if (column == null) { |
| throw new IllegalArgumentException("column=null"); |
| } |
| } |
| |
| /** |
| * Creates a SelectItem based on an expression. All expression-based |
| * SelectItems must have aliases. |
| * |
| * @param expression |
| * @param alias |
| */ |
| public SelectItem(String expression, String alias) { |
| this(null, expression, alias); |
| } |
| |
| /** |
| * Creates a SelectItem based on a function and an expression. All |
| * expression-based SelectItems must have aliases. |
| * |
| * @param function |
| * @param expression |
| * @param alias |
| */ |
| public SelectItem(FunctionType function, String expression, String alias) { |
| this(null, null, function, expression, null, alias, false); |
| if (expression == null) { |
| throw new IllegalArgumentException("expression=null"); |
| } |
| } |
| |
| /** |
| * Creates a SelectItem that references another select item in a subquery |
| * |
| * @param subQuerySelectItem |
| * @param subQueryFromItem |
| * the FromItem that holds the sub-query |
| */ |
| public SelectItem(SelectItem subQuerySelectItem, FromItem subQueryFromItem) { |
| this(null, subQueryFromItem, null, null, subQuerySelectItem, null, false); |
| if (subQueryFromItem.getSubQuery() == null) { |
| throw new IllegalArgumentException("Only sub-query based FromItems allowed."); |
| } |
| if (subQuerySelectItem.getQuery() != null |
| && !subQuerySelectItem.getQuery().equals(subQueryFromItem.getSubQuery())) { |
| throw new IllegalArgumentException("The SelectItem must exist in the sub-query"); |
| } |
| } |
| |
| public String getAlias() { |
| return _alias; |
| } |
| |
| public SelectItem setAlias(String alias) { |
| _alias = alias; |
| return this; |
| } |
| |
| /** |
| * |
| * @return |
| * @deprecated use {@link #getAggregateFunction()} or |
| * {@link #getScalarFunction()} instead |
| */ |
| @Deprecated |
| public FunctionType getFunction() { |
| return _function; |
| } |
| |
| public AggregateFunction getAggregateFunction() { |
| if (_function instanceof AggregateFunction) { |
| return (AggregateFunction) _function; |
| } |
| return null; |
| } |
| |
| public ScalarFunction getScalarFunction() { |
| if (_function instanceof ScalarFunction) { |
| return (ScalarFunction) _function; |
| } |
| return null; |
| } |
| |
| /** |
| * @return if this is a function based SelectItem where function calculation |
| * is allowed to be approximated (if the datastore type has an |
| * approximate calculation method). Approximated function results |
| * are as the name implies not exact, but might be valuable as an |
| * optimization in some cases. |
| */ |
| public boolean isFunctionApproximationAllowed() { |
| return _functionApproximationAllowed; |
| } |
| |
| public void setFunctionApproximationAllowed(boolean functionApproximationAllowed) { |
| _functionApproximationAllowed = functionApproximationAllowed; |
| } |
| |
| public Column getColumn() { |
| return _column; |
| } |
| |
| /** |
| * Tries to infer the {@link ColumnType} of this {@link SelectItem}. For |
| * expression based select items, this is not possible, and the method will |
| * return null. |
| * |
| * @return |
| */ |
| public ColumnType getExpectedColumnType() { |
| if (_subQuerySelectItem != null) { |
| return _subQuerySelectItem.getExpectedColumnType(); |
| } |
| if (_function != null) { |
| if (_column != null) { |
| return _function.getExpectedColumnType(_column.getType()); |
| } else { |
| return _function.getExpectedColumnType(null); |
| } |
| } |
| if (_column != null) { |
| return _column.getType(); |
| } |
| return null; |
| } |
| |
| public String getExpression() { |
| return _expression; |
| } |
| |
| public SelectItem setQuery(Query query) { |
| _query = query; |
| return this; |
| } |
| |
| public Query getQuery() { |
| return _query; |
| } |
| |
| public SelectItem getSubQuerySelectItem() { |
| return _subQuerySelectItem; |
| } |
| |
| /** |
| * @deprecated use {@link #getFromItem()} instead |
| */ |
| @Deprecated |
| public FromItem getSubQueryFromItem() { |
| return _fromItem; |
| } |
| |
| public FromItem getFromItem() { |
| return _fromItem; |
| } |
| |
| /** |
| * @return the name that this SelectItem can be referenced with, if |
| * referenced from a super-query. This will usually be the alias, |
| * but if there is no alias, then the column name will be used. |
| */ |
| public String getSuperQueryAlias() { |
| return getSuperQueryAlias(true); |
| } |
| |
| /** |
| * @return the name that this SelectItem can be referenced with, if |
| * referenced from a super-query. This will usually be the alias, |
| * but if there is no alias, then the column name will be used. |
| * |
| * @param includeQuotes |
| * indicates whether or not the output should include quotes, if |
| * the select item's column has quotes associated (typically |
| * true, but false if used for presentation) |
| */ |
| public String getSuperQueryAlias(boolean includeQuotes) { |
| if (_alias != null) { |
| return _alias; |
| } else if (_column != null) { |
| final StringBuilder sb = new StringBuilder(); |
| if (_function != null) { |
| if (_functionApproximationAllowed) { |
| sb.append(FUNCTION_APPROXIMATION_PREFIX); |
| } |
| sb.append(_function.getFunctionName()); |
| sb.append('('); |
| } |
| if (includeQuotes) { |
| sb.append(_column.getQuotedName()); |
| } else { |
| sb.append(_column.getName()); |
| } |
| if (_function != null) { |
| sb.append(')'); |
| } |
| return sb.toString(); |
| } else { |
| logger.debug("Could not resolve a reasonable super-query alias for SelectItem: {}", toSql()); |
| return toStringNoAlias().toString(); |
| } |
| } |
| |
| public String getSameQueryAlias() { |
| return getSameQueryAlias(false); |
| } |
| |
| /** |
| * @return an alias that can be used in WHERE, GROUP BY and ORDER BY clauses |
| * in the same query |
| */ |
| public String getSameQueryAlias(boolean includeSchemaInColumnPath) { |
| if (_column != null) { |
| StringBuilder sb = new StringBuilder(); |
| String columnPrefix = getToStringColumnPrefix(includeSchemaInColumnPath); |
| sb.append(columnPrefix); |
| sb.append(_column.getQuotedName()); |
| if (_function != null) { |
| if (_functionApproximationAllowed) { |
| sb.insert(0, FUNCTION_APPROXIMATION_PREFIX + _function.getFunctionName() + "("); |
| } else { |
| sb.insert(0, _function.getFunctionName() + "("); |
| } |
| sb.append(")"); |
| } |
| return sb.toString(); |
| } |
| String alias = getAlias(); |
| if (alias == null) { |
| alias = toStringNoAlias(includeSchemaInColumnPath).toString(); |
| logger.debug("Could not resolve a reasonable same-query alias for SelectItem: {}", toSql()); |
| } |
| return alias; |
| } |
| |
| @Override |
| public String toSql() { |
| return toSql(false); |
| } |
| |
| @Override |
| public String toSql(boolean includeSchemaInColumnPath) { |
| StringBuilder sb = toStringNoAlias(includeSchemaInColumnPath); |
| if (_alias != null) { |
| sb.append(" AS "); |
| sb.append(_alias); |
| } |
| return sb.toString(); |
| } |
| |
| public StringBuilder toStringNoAlias() { |
| return toStringNoAlias(false); |
| } |
| |
| public StringBuilder toStringNoAlias(boolean includeSchemaInColumnPath) { |
| StringBuilder sb = new StringBuilder(); |
| if (_column != null) { |
| sb.append(getToStringColumnPrefix(includeSchemaInColumnPath)); |
| sb.append(_column.getQuotedName()); |
| } |
| if (_expression != null) { |
| sb.append(_expression); |
| } |
| if (_fromItem != null && _subQuerySelectItem != null) { |
| if (_fromItem.getAlias() != null) { |
| sb.append(_fromItem.getAlias() + '.'); |
| } |
| sb.append(_subQuerySelectItem.getSuperQueryAlias()); |
| } |
| if (_function != null) { |
| if (_functionApproximationAllowed) { |
| sb.insert(0, FUNCTION_APPROXIMATION_PREFIX + _function.getFunctionName() + "("); |
| } else { |
| sb.insert(0, _function.getFunctionName() + "("); |
| } |
| sb.append(")"); |
| } |
| return sb; |
| } |
| |
| private String getToStringColumnPrefix(boolean includeSchemaInColumnPath) { |
| StringBuilder sb = new StringBuilder(); |
| if (_fromItem != null && _fromItem.getAlias() != null) { |
| sb.append(_fromItem.getAlias()); |
| sb.append('.'); |
| } else { |
| final Table table = _column.getTable(); |
| String tableLabel; |
| if (_query == null) { |
| tableLabel = null; |
| } else { |
| tableLabel = _query.getFromClause().getAlias(table); |
| } |
| if (table != null) { |
| if (tableLabel == null) { |
| tableLabel = table.getQuotedName(); |
| if (includeSchemaInColumnPath) { |
| Schema schema = table.getSchema(); |
| if (schema != null) { |
| tableLabel = schema.getQuotedName() + "." + tableLabel; |
| } |
| } |
| } |
| sb.append(tableLabel); |
| sb.append('.'); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| public boolean equalsIgnoreAlias(SelectItem that) { |
| return equalsIgnoreAlias(that, false); |
| } |
| |
| public boolean equalsIgnoreAlias(SelectItem that, boolean exactColumnCompare) { |
| if (that == null) { |
| return false; |
| } |
| if (that == this) { |
| return true; |
| } |
| |
| EqualsBuilder eb = new EqualsBuilder(); |
| if (exactColumnCompare) { |
| eb.append(this._column == that._column); |
| eb.append(this._fromItem, that._fromItem); |
| } else { |
| eb.append(this._column, that._column); |
| } |
| eb.append(this._function, that._function); |
| eb.append(this._functionApproximationAllowed, that._functionApproximationAllowed); |
| eb.append(this._expression, that._expression); |
| if (_subQuerySelectItem != null) { |
| eb.append(_subQuerySelectItem.equalsIgnoreAlias(that._subQuerySelectItem)); |
| } else { |
| if (that._subQuerySelectItem != null) { |
| eb.append(false); |
| } |
| } |
| return eb.isEquals(); |
| } |
| |
| @Override |
| protected void decorateIdentity(List<Object> identifiers) { |
| identifiers.add(_expression); |
| identifiers.add(_alias); |
| identifiers.add(_column); |
| identifiers.add(_function); |
| identifiers.add(_functionApproximationAllowed); |
| identifiers.add(_fromItem); |
| identifiers.add(_subQuerySelectItem); |
| } |
| |
| @Override |
| protected SelectItem clone() { |
| final SelectItem subQuerySelectItem = (_subQuerySelectItem == null ? null : _subQuerySelectItem.clone()); |
| final FromItem fromItem = (_fromItem == null ? null : _fromItem.clone()); |
| final SelectItem s = new SelectItem(_column, fromItem, _function, _expression, subQuerySelectItem, _alias, |
| _functionApproximationAllowed); |
| return s; |
| } |
| |
| /** |
| * Creates a copy of the {@link SelectItem}, with a different |
| * {@link FunctionType}. |
| * |
| * @param function |
| * @return |
| */ |
| public SelectItem replaceFunction(FunctionType function) { |
| return new SelectItem(_column, _fromItem, function, _expression, _subQuerySelectItem, _alias, |
| _functionApproximationAllowed); |
| } |
| |
| /** |
| * Creates a copy of the {@link SelectItem}, with a different |
| * {@link #isFunctionApproximationAllowed()} flag set. |
| * |
| * @param functionApproximationAllowed |
| * @return |
| */ |
| public SelectItem replaceFunctionApproximationAllowed(boolean functionApproximationAllowed) { |
| return new SelectItem(_column, _fromItem, _function, _expression, _subQuerySelectItem, _alias, |
| functionApproximationAllowed); |
| } |
| |
| /** |
| * Investigates whether or not this SelectItem references a particular |
| * column. This will search for direct references and indirect references |
| * via subqueries. |
| * |
| * @param column |
| * @return a boolean that is true if the specified column is referenced by |
| * this SelectItem and false otherwise. |
| */ |
| public boolean isReferenced(Column column) { |
| if (column != null) { |
| if (column.equals(_column)) { |
| return true; |
| } |
| if (_subQuerySelectItem != null) { |
| return _subQuerySelectItem.isReferenced(column); |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public String toString() { |
| return toSql(); |
| } |
| } |