blob: fec78cc3de5345972a50bdbd9efad5d0f8be6843 [file] [log] [blame]
/*
Derby - Class org.apache.derby.impl.sql.compile.ResultColumn
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.derby.impl.sql.compile;
import java.util.List;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.reference.SQLState;
import org.apache.derby.iapi.services.compiler.MethodBuilder;
import org.apache.derby.iapi.services.context.ContextManager;
import org.apache.derby.iapi.services.io.StoredFormatIds;
import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.sql.ResultColumnDescriptor;
import org.apache.derby.iapi.sql.compile.Visitor;
import org.apache.derby.iapi.sql.dictionary.ColumnDescriptor;
import org.apache.derby.iapi.sql.dictionary.TableDescriptor;
import org.apache.derby.iapi.store.access.Qualifier;
import org.apache.derby.iapi.types.DataTypeDescriptor;
import org.apache.derby.iapi.types.DataValueDescriptor;
import org.apache.derby.iapi.types.DataValueFactory;
import org.apache.derby.iapi.types.StringDataValue;
import org.apache.derby.iapi.types.TypeId;
import org.apache.derby.iapi.util.StringUtil;
/**
* A ResultColumn represents a result column in a SELECT, INSERT, or UPDATE
* statement. In a SELECT statement, the result column just represents an
* expression in a row being returned to the client. For INSERT and UPDATE
* statements, the result column represents an column in a stored table.
* So, a ResultColumn has to be bound differently depending on the type of
* statement it appears in.
* <P>
* The type of the ResultColumn can differ from its underlying expression,
* for example in certain joins the ResultColumn can be nullable even if
* its underlying column is not. In an INSERT or UPDATE the ResultColumn
* will represent the type of the column in the table, the type of
* the underlying expression will be the type of the source of the
* value to be insert or updated. The method columnTypeAndLengthMatch()
* can be used to detect when normalization is required between
* the expression and the type of ResultColumn. This class does
* not implement any type normalization (conversion), this is
* typically handled by a NormalizeResultSetNode.
*
*/
class ResultColumn extends ValueNode
implements ResultColumnDescriptor, Comparable<ResultColumn>
{
/* _underlyingName and _derivedColumnName should point to the same string, unless there is a
* derived column list, in which case _underlyingName will point to the underlying name
* and _derivedColumnName will point to the name from the derived column list.
*/
private String _underlyingName; // name from the actual data source
private String _derivedColumnName;
private String _unqualifiedTableName;
private String _unqualifiedSourceTableName;
//Used by metadata api ResultSetMetaData.getSchemaName to get a column's table's schema.
private String _sourceSchemaName;
private ValueNode _expression;
private ColumnDescriptor _columnDescriptor;
private boolean _isGenerated;
private boolean _isGeneratedForUnmatchedColumnInInsert;
private boolean _isGroupingColumn;
private boolean _isReferenced;
private boolean _isRedundant;
private boolean _isNameGenerated;
private boolean _updated;
private boolean _updatableByCursor;
private boolean defaultColumn;
private boolean wasDefault;
//Following 2 fields have been added for DERBY-4631.
//rightOuterJoinUsingClause will be set to true for following 2 cases
//1)if this column represents the join column which is part of the
// SELECT list of a RIGHT OUTER JOIN with USING/NATURAL. eg
// select c from t1 right join t2 using (c)
// This case is talking about column c as in "select c"
//2)if this column represents the join column from the right table
// for predicates generated for the USING/NATURAL of RIGHT OUTER JOIN
// eg
// select c from t1 right join t2 using (c)
// For "using(c)", a join predicate will be created as follows
// t1.c=t2.c
// This case is talking about column t2.c of the join predicate.
private boolean rightOuterJoinUsingClause;
//Following will be non-null for the case 1) above. It will show the
// association of this result column to the join resultset created
// for the RIGHT OUTER JOIN with USING/NATURAL. This information along
// with rightOuterJoinUsingClause will be used during the code generation
// time.
private JoinNode joinResultSet = null;
// tells us if this ResultColumn is a placeholder for a generated
// autoincrement value for an insert statement.
private boolean _autoincrementGenerated;
// tells us if this ResultColumn represents an autoincrement column in a
// base table.
private boolean _autoincrement;
/* ResultSetNumber for the ResultSet (at generate() time) that we belong to */
private int resultSetNumber = -1;
private ColumnReference _reference; // used to verify quals at bind time, if given.
/* virtualColumnId is the ResultColumn's position (1-based) within the ResultSet */
private int virtualColumnId;
ResultColumn(ContextManager cm) {
super(cm);
}
/**
* @param underlyingName The name of the column, if any.
* @param expression The expression this result column represents
* @param cm context manager
*/
ResultColumn(
String underlyingName,
ValueNode expression,
ContextManager cm) throws StandardException {
super(cm);
setTypeExpressionAndDefault(expression);
_underlyingName = underlyingName;
_derivedColumnName = _underlyingName;
}
/**
* @param cr A column reference node
* @param expression The expression this result column represents
* @param cm context manager
* @throws StandardException
*/
ResultColumn(
ColumnReference cr,
ValueNode expression,
ContextManager cm) throws StandardException {
super(cm);
setTypeExpressionAndDefault(expression);
_underlyingName = cr.getColumnName();
_derivedColumnName = cr.getColumnName();
// When we bind, we'll want to make sure the reference has the right
// table name.
_reference = cr;
}
/**
* @param cd The column descriptor
* @param expression The expression this result column represents
* @param cm context manager
* @throws StandardException
*/
ResultColumn(
ColumnDescriptor cd,
ValueNode expression,
ContextManager cm) throws StandardException {
super(cm);
setTypeExpressionAndDefault(expression);
_underlyingName = cd.getColumnName();
_derivedColumnName = _underlyingName;
setType(cd.getType());
_columnDescriptor = cd;
_autoincrement = cd.isAutoincrement();
}
/**
* @param dtd The type of the column
* @param expression The expression this result column represents
* @param cm context manager
* @throws StandardException
*/
ResultColumn(
DataTypeDescriptor dtd,
ValueNode expression,
ContextManager cm) throws StandardException {
super(cm);
setTypeExpressionAndDefault(expression);
setType(dtd);
if (_expression instanceof ColumnReference) {
_reference = (ColumnReference)expression;
}
}
private void setTypeExpressionAndDefault(ValueNode expression) {
setExpression(expression);
if (expression != null &&
expression instanceof DefaultNode) {
// This result column represents a <default> keyword in an insert or
// update statement
defaultColumn = true;
}
}
/**
* Returns TRUE if the ResultColumn is join column for a RIGHT OUTER
* JOIN with USING/NATURAL. More comments at the top of this class
* where rightOuterJoinUsingClause is defined.
*/
boolean isRightOuterJoinUsingClause()
{
return rightOuterJoinUsingClause;
}
/**
* Will be set to TRUE if this ResultColumn is join column for a
* RIGHT OUTER JOIN with USING/NATURAL. More comments at the top of
* this class where rightOuterJoinUsingClause is defined. 2 eg cases
* 1)select c from t1 right join t2 using (c)
* This case is talking about column c as in "select c"
* 2)select c from t1 right join t2 using (c)
* For "using(c)", a join predicate will be created as follows
* t1.c=t2.c
* This case is talking about column t2.c of the join predicate.
*
* This method gets called for Case 1) during the bind phase of
* ResultColumn(ResultColumn.bindExpression).
*
* This method gets called for Case 2) during the bind phase of
* JoinNode while we are going through the list of join columns
* for a NATURAL JOIN or user supplied list of join columns for
* USING clause(JoinNode.getMatchingColumn).
*
* @param value True/False
*/
void setRightOuterJoinUsingClause(boolean value)
{
rightOuterJoinUsingClause = value;
}
/**
* Returns a non-null value if the ResultColumn represents the join
* column which is part of the SELECT list of a RIGHT OUTER JOIN with
* USING/NATURAL. eg
* select c from t1 right join t2 using (c)
* The join column we are talking about is column c as in "select c"
* The return value of following method will show the association of this
* result column to the join resultset created for the RIGHT OUTER JOIN
* with USING/NATURAL. This information along with
* rightOuterJoinUsingClause will be used during the code generation
* time.
*/
JoinNode getJoinResultSet() {
return joinResultSet;
}
/**
* This method gets called during the bind phase of a ResultColumn if it
* is determined that the ResultColumn represents the join column which
* is part of the SELECT list of a RIGHT OUTER JOIN with
* USING/NATURAL. eg
* select c from t1 right join t2 using (c)
* This case is talking about column c as in "select c"
* @param resultSet - The ResultColumn belongs to this JoinNode
*/
void setJoinResultset(JoinNode resultSet)
{
joinResultSet = resultSet;
}
/**
* Returns TRUE if the ResultColumn is standing in for a DEFAULT keyword in
* an insert/update statement.
*/
boolean isDefaultColumn()
{
return defaultColumn;
}
void setDefaultColumn(boolean value)
{
defaultColumn = value;
}
/**
* Returns TRUE if the ResultColumn used to stand in for a DEFAULT keyword in
* an insert/update statement.
*/
boolean wasDefaultColumn()
{
return wasDefault;
}
void setWasDefaultColumn(boolean value)
{
wasDefault = value;
}
/**
* Return TRUE if this result column matches the provided column name.
*
* This function is used by ORDER BY column resolution. For the
* ORDER BY clause, Derby will prefer to match on the column's
* alias (_derivedColumnName), but will also successfully match on the
* underlying column name. Thus the following statements are
* treated equally:
* select name from person order by name;
* select name as person_name from person order by name;
* select name as person_name from person order by person_name;
* See DERBY-2351 for more discussion.
*/
boolean columnNameMatches(String columnName)
{
return columnName.equals(_derivedColumnName) ||
columnName.equals( _underlyingName ) ||
columnName.equals(getSourceColumnName());
}
/**
* Get non-null column name. This method is called during the bind phase
* to see if we are dealing with ResultColumn in the SELECT list that
* belongs to a RIGHT OUTER JOIN(NATURAL OR USING)'s join column.
*
* For a query like following, we want to use column name x and not the
* alias x1 when looking in the JoinNode for join column
* SELECT x x1
* FROM derby4631_t2 NATURAL RIGHT OUTER JOIN derby4631_t1;
* For a query like following, getSourceColumnName() will return null
* because we are dealing with a function for the column. For this
* case, "name" will return the alias name cx
* SELECT coalesce(derby4631_t2.x, derby4631_t1.x) cx
* FROM derby4631_t2 NATURAL RIGHT OUTER JOIN derby4631_t1;
* For a query like following, getSourceColumnName() and name will
* return null and hence need to use the generated name
* SELECT ''dummy="'|| TRIM(CHAR(x))|| '"'
* FROM (derby4631_t2 NATURAL RIGHT OUTER JOIN derby4631_t1);
*/
String getUnderlyingOrAliasName()
{
if (getSourceColumnName() != null)
{
return getSourceColumnName();
}
else if ( _underlyingName != null )
{
return _underlyingName;
}
else
{
return _derivedColumnName;
}
}
/**
* Returns true if this column is updatable.
*
* This method is used for determining if updateRow and insertRow
* are allowed for this cursor (DERBY-1773). Since the updateRow
* and insertRow implementations dynamically build SQL statements
* on the fly, the critical issue here is whether we have a
* column that has been aliased, because if it has been
* aliased, the dynamic SQL generation logic won't be able to
* compute the proper true base column name when it needs to.
*
* @return true if this result column is updatable.
*/
boolean isUpdatable()
{
return _derivedColumnName == null ||
_underlyingName.equals(_derivedColumnName);
}
/**
* Returns the underlying source column name, if this ResultColumn
* is a simple direct reference to a table column, or NULL otherwise.
*/
String getSourceColumnName()
{
if (_expression instanceof ColumnReference)
return ((ColumnReference)_expression).getColumnName();
return null;
}
/**
* The following methods implement the ResultColumnDescriptor
* interface. See the Language Module Interface for details.
*/
public String getName()
{
return _derivedColumnName;
}
@Override
String getSchemaName() throws StandardException
{
if ((_columnDescriptor!=null) &&
(_columnDescriptor.getTableDescriptor() != null))
return _columnDescriptor.getTableDescriptor().getSchemaName();
else
{
if (_expression != null)
// REMIND: could look in reference, if set.
return _expression.getSchemaName();
else
return null;
}
}
@Override
String getTableName()
{
if (_unqualifiedTableName != null)
{
return _unqualifiedTableName;
}
if ((_columnDescriptor!=null) &&
(_columnDescriptor.getTableDescriptor() != null))
{
return _columnDescriptor.getTableDescriptor().getName();
}
else
{
return _expression.getTableName();
}
}
/**
* @see ResultColumnDescriptor#getSourceTableName
*/
public String getSourceTableName()
{
return _unqualifiedSourceTableName;
}
/**
* @see ResultColumnDescriptor#getSourceSchemaName
*/
public String getSourceSchemaName()
{
return _sourceSchemaName;
}
/**
* Clear the table name for the underlying ColumnReference.
* See UpdateNode.scrubResultColumns() for full explaination.
*/
void clearTableName()
{
if (_expression instanceof ColumnReference)
{
((ColumnReference) _expression).setQualifiedTableName( (TableName) null );
}
}
public DataTypeDescriptor getType()
{
return getTypeServices();
}
public int getColumnPosition()
{
if (_columnDescriptor!=null)
return _columnDescriptor.getPosition();
else
return virtualColumnId;
}
/**
* Set the expression in this ResultColumn. This is useful in those
* cases where you don't know the expression in advance, like for
* INSERT statements with column lists, where the column list and
* SELECT or VALUES clause are parsed separately, and then have to
* be hooked up.
*
* @param expression The expression to be set in this ResultColumn
*/
void setExpression(ValueNode expression)
{
_expression = expression;
}
/**
* Get the expression in this ResultColumn.
*
* @return ValueNode this.expression
*/
ValueNode getExpression()
{
return _expression;
}
/**
* Set the expression to a null node of the
* correct type.
*
* @exception StandardException Thrown on error
*/
void setExpressionToNullNode()
throws StandardException
{
setExpression( getNullNode(getTypeServices()) );
}
/**
* Set the name in this ResultColumn. This is useful when you don't
* know the name at the time you create the ResultColumn, for example,
* in an insert-select statement, where you want the names of the
* result columns to match the table being inserted into, not the
* table they came from.
*
* @param name The name to set in this ResultColumn
*/
void setName(String name)
{
if ( _underlyingName == null )
{
_underlyingName = name;
}
else {
if (SanityManager.DEBUG)
SanityManager.ASSERT(_reference == null ||
name.equals(_reference.getColumnName()),
"don't change name from reference name");
}
_derivedColumnName = name;
}
/**
* Is the name for this ResultColumn generated?
*/
boolean isNameGenerated()
{
return _isNameGenerated;
}
/**
* Set that this result column name is generated.
*/
void setNameGenerated(boolean value)
{
_isNameGenerated = value;
}
/**
* Set the resultSetNumber for this ResultColumn. This is the
* resultSetNumber for the ResultSet that we belong to. This
* is useful for generate() and necessary since we do not have a
* back pointer to the RSN.
*
* @param resultSetNumber The resultSetNumber.
*/
void setResultSetNumber(int resultSetNumber)
{
this.resultSetNumber = resultSetNumber;
}
/**
* Get the resultSetNumber for this ResultColumn.
*
* @return int The resultSetNumber.
*/
public int getResultSetNumber()
{
return resultSetNumber;
}
/**
* Adjust the virtualColumnId for this ResultColumn by the specified amount
*
* @param adjust The adjustment for the virtualColumnId
*/
void adjustVirtualColumnId(int adjust)
{
virtualColumnId += adjust;
}
/**
* Set the virtualColumnId for this ResultColumn
*
* @param id The virtualColumnId for this ResultColumn
*/
void setVirtualColumnId(int id)
{
virtualColumnId = id;
}
/**
* Get the virtualColumnId for this ResultColumn
*
* @return virtualColumnId for this ResultColumn
*/
int getVirtualColumnId()
{
return virtualColumnId;
}
/**
* Adjust this virtualColumnId to account for the removal of a column
*
* This routine is called when bind processing finds and removes
* duplicate columns in the result list which were pulled up due to their
* presence in the ORDER BY clause, but were later found to be duplicate.
*
* If this column is a virtual column, and if this column's virtual
* column id is greater than the column id which is being removed, then
* we must logically shift this column to the left by decrementing its
* virtual column id.
*
* @param removedColumnId id of the column being removed.
*/
void collapseVirtualColumnIdGap(int removedColumnId)
{
if (_columnDescriptor == null && virtualColumnId > removedColumnId)
virtualColumnId--;
}
/**
* Generate a unique (across the entire statement) column name for unnamed
* ResultColumns
*
* @exception StandardException Thrown on error
*/
void guaranteeColumnName() throws StandardException
{
if (_derivedColumnName == null)
{
/* Unions may also need generated names, if both sides name don't match */
_derivedColumnName ="SQLCol" + getCompilerContext().getNextColumnNumber();
_isNameGenerated = true;
}
}
/**
* Convert this object to a String. See comments in QueryTreeNode.java
* for how this should be done for tree printing.
*
* @return This object as a String
*/
@Override
public String toString()
{
if (SanityManager.DEBUG)
{
return "derivedColumnName: " + _derivedColumnName + "\n" +
"underlyingName: " + _underlyingName + "\n" +
"tableName: " + _unqualifiedTableName + "\n" +
"isDefaultColumn: " + defaultColumn + "\n" +
"wasDefaultColumn: " + wasDefault + "\n" +
"isNameGenerated: " + _isNameGenerated + "\n" +
"sourceTableName: " + _unqualifiedSourceTableName + "\n" +
"type: " + getTypeServices() + "\n" +
"columnDescriptor: " + _columnDescriptor + "\n" +
"isGenerated: " + _isGenerated + "\n" +
"isGeneratedForUnmatchedColumnInInsert: " + _isGeneratedForUnmatchedColumnInInsert + "\n" +
"isGroupingColumn: " + _isGroupingColumn + "\n" +
"isReferenced: " + _isReferenced + "\n" +
"isRedundant: " + _isRedundant + "\n" +
"rightOuterJoinUsingClause: " + rightOuterJoinUsingClause + "\n" +
"joinResultSet: " + joinResultSet + "\n" +
"virtualColumnId: " + virtualColumnId + "\n" +
"resultSetNumber: " + resultSetNumber + "\n" +
super.toString();
}
else
{
return "";
}
}
/**
* Prints the sub-nodes of this object. See QueryTreeNode.java for
* how tree printing is supposed to work.
*
* @param depth The depth of this node in the tree
*/
@Override
void printSubNodes(int depth)
{
if (SanityManager.DEBUG)
{
super.printSubNodes(depth);
if (_expression != null)
{
printLabel(depth, "expression: ");
_expression.treePrint(depth + 1);
}
if (_reference != null)
{
printLabel(depth, "reference: ");
_reference.treePrint(depth + 1);
}
}
}
/**
* Bind this expression. This means binding the sub-expressions.
* In this case, we figure out what the result type of this result
* column is when we call one of the bindResultColumn*() methods.
* The reason is that there are different ways of binding the
* result columns depending on the statement type, and this is
* a standard interface that does not take the statement type as
* a parameter.
*
* @param fromList The FROM list for the query this
* expression is in, for binding columns.
* @param subqueryList The subquery list being built as we find SubqueryNodes
* @param aggregates The aggregate list being built as we find AggregateNodes
*
* @return The new top of the expression tree.
*
* @exception StandardException Thrown on error
*/
@Override
ResultColumn bindExpression(FromList fromList, SubqueryList subqueryList, List<AggregateNode> aggregates)
throws StandardException
{
/*
** Set the type of a parameter to the type of the result column.
** Don't do it if this result column doesn't have a type yet.
** This can happen if the parameter is part of a table constructor.
*/
if (_expression.requiresTypeFromContext())
{
if (getTypeServices() != null)
{
_expression.setType(getTypeServices());
}
}
//DERBY-4631
//Following code is for a join column(which obviously will not be
// qualified with a table name because join columns are not
// associated with left or right table) of RIGHT OUTER JOIN
// with USING/NATURAL join. For such columns,
// isJoinColumnForRightOuterJoin() call will set
// rightOuterJoinUsingClause to true and associate the
// JoinResultSet with it. eg
// select c from t1 right join t2 using (c)
// Here, we are talking about column c as in "select c"
if (_expression.getTableName() == null) {
fromList.isJoinColumnForRightOuterJoin(this);
}
setExpression( _expression.bindExpression(fromList, subqueryList,
aggregates) );
if (_expression instanceof ColumnReference)
{
_autoincrement = ((ColumnReference)_expression).getSource().isAutoincrement();
}
return this;
}
/**
* Bind this result column by ordinal position and set the VirtualColumnId.
* This is useful for INSERT statements like "insert into t values (1, 2, 3)",
* where the user did not specify a column list.
* If a columnDescriptor is not found for a given position, then
* the user has specified more values than the # of columns in
* the table and an exception is thrown.
*
* NOTE: We must set the VirtualColumnId here because INSERT does not
* construct the ResultColumnList in the usual way.
*
* @param tableDescriptor The descriptor for the table being
* inserted into
* @param columnId The ordinal position of the column
* in the table, starting at 1.
*
* @exception StandardException Thrown on error
*/
void bindResultColumnByPosition(TableDescriptor tableDescriptor,
int columnId)
throws StandardException
{
ColumnDescriptor colDesc;
colDesc = tableDescriptor.getColumnDescriptor(columnId);
if (colDesc == null)
{
String errorString;
String schemaName;
errorString = "";
schemaName = tableDescriptor.getSchemaName();
if (schemaName != null)
errorString += schemaName + ".";
errorString += tableDescriptor.getName();
throw StandardException.newException(SQLState.LANG_TOO_MANY_RESULT_COLUMNS, errorString);
}
setColumnDescriptor(tableDescriptor, colDesc);
setVirtualColumnId(columnId);
}
/**
* Bind this result column by its name and set the VirtualColumnId.
* This is useful for update statements, and for INSERT statements
* like "insert into t (a, b, c) values (1, 2, 3)" where the user
* specified a column list.
* An exception is thrown when a columnDescriptor cannot be found for a
* given name. (There is no column with that name.)
*
* NOTE: We must set the VirtualColumnId here because INSERT does not
* construct the ResultColumnList in the usual way.
*
* @param tableDescriptor The descriptor for the table being
* updated or inserted into
* @param columnId The ordinal position of the column
* in the table, starting at 1. (Used to
* set the VirtualColumnId.)
*
* @exception StandardException Thrown on error
*/
void bindResultColumnByName(TableDescriptor tableDescriptor,
int columnId)
throws StandardException
{
ColumnDescriptor colDesc;
colDesc = tableDescriptor.getColumnDescriptor(_derivedColumnName);
if (colDesc == null)
{
String errorString;
String schemaName;
errorString = "";
schemaName = tableDescriptor.getSchemaName();
if (schemaName != null)
errorString += schemaName + ".";
errorString += tableDescriptor.getName();
throw StandardException.newException(SQLState.LANG_COLUMN_NOT_FOUND_IN_TABLE, _derivedColumnName, errorString);
}
setColumnDescriptor(tableDescriptor, colDesc);
setVirtualColumnId(columnId);
if (isPrivilegeCollectionRequired())
getCompilerContext().addRequiredColumnPriv( colDesc);
}
/**
* Change an untyped null to a typed null.
*
* @exception StandardException Thrown on error
*/
void typeUntypedNullExpression( ResultColumn bindingRC)
throws StandardException
{
TypeId typeId = bindingRC.getTypeId();
/* This is where we catch null in a VALUES clause outside
* of INSERT VALUES()
*/
if (typeId == null)
{
throw StandardException.newException(SQLState.LANG_NULL_IN_VALUES_CLAUSE);
}
if( _expression instanceof UntypedNullConstantNode)
//since we don't know the type of such a constant node, we just
//use the default values for collation type and derivation.
//eg insert into table1 values(1,null)
//When this method is executed for the sql above, we don't know
//the type of the null at this point.
setExpression( getNullNode(bindingRC.getTypeServices()) );
else if( ( _expression instanceof ColumnReference) && _expression.getTypeServices() == null)
{
// The expression must be a reference to a null column in a values table.
_expression.setType( bindingRC.getType());
}
}
/**
* Set the column descriptor for this result column. It also gets
* the data type services from the column descriptor and stores it in
* this result column: this is redundant, but we have to store the result
* type here for SELECT statements, and it is more orthogonal if the type
* can be found here regardless of what type of statement it is.
*
* @param tableDescriptor The TableDescriptor for the table
* being updated or inserted into.
* This parameter is used only for
* error reporting.
* @param columnDescriptor The ColumnDescriptor to set in
* this ResultColumn.
*
* @exception StandardException tableNameMismatch
*/
void setColumnDescriptor(TableDescriptor tableDescriptor,
ColumnDescriptor columnDescriptor) throws StandardException
{
if ( columnDescriptor != null ) { setType(columnDescriptor.getType()); }
_columnDescriptor = columnDescriptor;
/*
If the node was created using a reference, the table name
of the reference must agree with that of the tabledescriptor.
*/
if (
_reference != null &&
_reference.getTableName() != null &&
(_reference.getMergeTableID() == ColumnReference.MERGE_UNKNOWN)
)
{
if ( (tableDescriptor != null) && ! tableDescriptor.getName().equals(
_reference.getTableName()) )
{
/* REMIND: need to have schema name comparison someday as well...
** left out for now, lots of null checking needed...
** || ! tableDescriptor.getSchemaName().equals(
** reference.getQualifiedTableName().getSchemaName())) {
*/
String realName = tableDescriptor.getName();
String refName = _reference.getTableName();
throw StandardException.newException(SQLState.LANG_TABLE_NAME_MISMATCH,
realName, refName);
}
}
}
/**
* Bind the result column to the expression that lives under it.
* All this does is copy the datatype information to this node.
* This is useful for SELECT statements, where the result type
* of each column is the type of the column's expression.
*
* @exception StandardException Thrown on error
*/
void bindResultColumnToExpression()
throws StandardException
{
/*
** This gets the same DataTypeServices object as
** is used in the expression. It is probably not
** necessary to clone the object here.
*/
setType(_expression.getTypeServices());
if (_expression instanceof ColumnReference)
{
ColumnReference cr = (ColumnReference) _expression;
_unqualifiedTableName = cr.getTableName();
_unqualifiedSourceTableName = cr.getSourceTableName();
_sourceSchemaName = cr.getSourceSchemaName();
}
}
/**
* Set the column source's table name
* @param t The source table name
*/
void setSourceTableName(String t) {
_unqualifiedSourceTableName = t;
}
/**
* Set the column source's schema name
* @param s The source schema name
*/
void setSourceSchemaName(String s) {
_sourceSchemaName = s;
}
/**
* Preprocess an expression tree. We do a number of transformations
* here (including subqueries, IN lists, LIKE and BETWEEN) plus
* subquery flattening.
* NOTE: This is done before the outer ResultSetNode is preprocessed.
*
* @param numTables Number of tables in the DML Statement
* @param outerFromList FromList from outer query block
* @param outerSubqueryList SubqueryList from outer query block
* @param outerPredicateList PredicateList from outer query block
*
* @return The modified expression
*
* @exception StandardException Thrown on error
*/
@Override
ResultColumn preprocess(int numTables,
FromList outerFromList,
SubqueryList outerSubqueryList,
PredicateList outerPredicateList)
throws StandardException
{
if (_expression == null)
return this;
setExpression( _expression.preprocess(numTables, outerFromList,
outerSubqueryList,
outerPredicateList) );
return this;
}
/**
This verifies that the expression is storable into the result column.
It checks versus the given ResultColumn.
This method should not be called until the result column and
expression both have a valid type, i.e. after they are bound
appropriately. Its use is for statements like insert, that need to
verify if a given value can be stored into a column.
@exception StandardException thrown if types not suitable.
*/
void checkStorableExpression(ResultColumn toStore)
throws StandardException
{
checkStorableExpression((ValueNode) toStore);
}
private void checkStorableExpression(ValueNode source)
throws StandardException
{
TypeId toStoreTypeId = source.getTypeId();
if (!getTypeCompiler().storable(toStoreTypeId, getClassFactory()))
{
throw StandardException.newException(SQLState.LANG_NOT_STORABLE,
getTypeId().getSQLTypeName(),
toStoreTypeId.getSQLTypeName() );
}
}
/**
This verifies that the expression is storable into the result column.
It checks versus the expression under this ResultColumn.
This method should not be called until the result column and
expression both have a valid type, i.e. after they are bound
appropriately. Its use is for statements like update, that need to
verify if a given value can be stored into a column.
@exception StandardException thrown if types not suitable.
*/
void checkStorableExpression()
throws StandardException
{
checkStorableExpression(getExpression());
}
/**
* Do code generation for a result column. This consists of doing the code
* generation for the underlying expression.
*
* @param ecb The ExpressionClassBuilder for the class we're generating
* @param mb The method the expression will go into
*
*
* @exception StandardException Thrown on error
*/
@Override
void generateExpression(ExpressionClassBuilder ecb, MethodBuilder mb)
throws StandardException
{
_expression.generateExpression(ecb, mb);
}
/**
* Do code generation to return a Null of the appropriate type
* for the result column.
Requires the getCOlumnExpress value pushed onto the stack
*
* @param acb The ActivationClassBuilder for the class we're generating
* @param eb The ExpressionBlock that the generate code is to go into
* @param getColumnExpression "fieldx.getColumn(y)"
*
* @exception StandardException Thrown on error
*/
/*PUSHCOMPILE
void generateNulls(ExpressionClassBuilder acb,
MethodBuilder mb,
Expression getColumnExpress)
throws StandardException
{
acb.pushDataValueFactory(mb);
getTypeCompiler().generateNull(mb, acb.getBaseClassName());
mb.cast(ClassName.DataValueDescriptor);
return eb.newCastExpression(
ClassName.DataValueDescriptor,
getTypeCompiler().
generateNull(
eb,
acb.getBaseClassName(),
acb.getDataValueFactory(eb),
getColumnExpress));
}
*/
/**
** Check whether the column length and type of this result column
** match the expression under the columns. This is useful for
** INSERT and UPDATE statements. For SELECT statements this method
** should always return true. There is no need to call this for a
** DELETE statement.
**
** @return true means the column matches its expressions,
** false means it doesn't match.
*/
boolean columnTypeAndLengthMatch()
throws StandardException
{
/*
** We can never make any assumptions about
** parameters. So don't even bother in this
** case.
*/
if (getExpression().requiresTypeFromContext())
{
return false;
}
// Are we inserting/updating an XML column? If so, we always
// return false so that normalization will occur. We have to
// do this because there are different "kinds" of XML values
// and we need to make sure they match--but we don't know
// the "kind" until execution time. See the "normalize"
// method in org.apache.derby.iapi.types.XML for more.
if (getTypeId().isXMLTypeId())
return false;
DataTypeDescriptor expressionType = getExpression().getTypeServices();
if (!getTypeServices().isExactTypeAndLengthMatch(expressionType))
return false;
/* Is the source nullable and the target non-nullable? */
if ((! getTypeServices().isNullable()) && expressionType.isNullable())
{
return false;
}
return true;
}
boolean columnTypeAndLengthMatch(ResultColumn otherColumn)
throws StandardException
{
ValueNode otherExpression = otherColumn.getExpression();
DataTypeDescriptor resultColumnType = getTypeServices();
DataTypeDescriptor otherResultColumnType = otherColumn.getTypeServices();
if (SanityManager.DEBUG)
{
SanityManager.ASSERT(resultColumnType != null,
"Type is null for column " + this);
SanityManager.ASSERT(otherResultColumnType != null,
"Type is null for column " + otherColumn);
}
/*
** We can never make any assumptions about
** parameters. So don't even bother in this
** case.
*/
if ((otherExpression != null) && (otherExpression.requiresTypeFromContext()) ||
(_expression.requiresTypeFromContext()))
{
return false;
}
// Are we inserting/updating an XML column? If so, we always
// return false so that normalization will occur. We have to
// do this because there are different "kinds" of XML values
// and we need to make sure they match--but we don't know
// the "kind" until execution time. See the "normalize"
// method in org.apache.derby.iapi.types.XML for more.
if (resultColumnType.getTypeId().isXMLTypeId())
return false;
/* Are they the same type? */
if ( ! resultColumnType.getTypeId().equals(
otherResultColumnType.getTypeId()
)
)
{
/* If the source is a constant of a different type then
* we try to convert that constant to a constant of our
* type. (The initial implementation only does the conversion
* to string types because the most common problem is a char
* constant with a varchar column.)
* NOTE: We do not attempt any conversion here if the source
* is a string type and the target is not or vice versa in
* order to avoid problems with implicit varchar conversions.
* Anyway, we will check if the "converted" constant has the
* same type as the original constant. If not, then the conversion
* happened. In that case, we will reuse the ConstantNode, for simplicity,
* and reset the type to match the desired type.
*/
if (otherExpression instanceof ConstantNode)
{
ConstantNode constant = (ConstantNode)otherColumn.getExpression();
DataValueDescriptor oldValue = constant.getValue();
DataValueDescriptor newValue = convertConstant(
resultColumnType.getTypeId(),
resultColumnType.getMaximumWidth(),
oldValue);
if ((oldValue != newValue) &&
(oldValue instanceof StringDataValue ==
newValue instanceof StringDataValue))
{
constant.setValue(newValue);
constant.setType(getTypeServices());
otherColumn.bindResultColumnToExpression();
otherResultColumnType = otherColumn.getType();
}
//If we are dealing with StringDataValue, then make sure we
//have correct collation type and derivaiton set and the value
//represented by collation is either SQLxxx or CollatorSQLxxx
//depending on the collation type.
if (newValue instanceof StringDataValue)
{
constant.setCollationInfo(resultColumnType);
DataValueFactory dvf = getDataValueFactory();
newValue = ((StringDataValue)newValue).getValue(dvf.getCharacterCollator(
constant.getTypeServices().getCollationType()));
constant.setValue(newValue);
}
}
if ( ! resultColumnType.getTypeId().equals(
otherResultColumnType.getTypeId()
)
)
{
return false;
}
}
/* Are they the same precision? */
if (resultColumnType.getPrecision() !=
otherResultColumnType.getPrecision())
{
return false;
}
/* Are they the same scale? */
if (resultColumnType.getScale() != otherResultColumnType.getScale())
{
return false;
}
/* Are they the same width? */
if (resultColumnType.getMaximumWidth() !=
otherResultColumnType.getMaximumWidth())
{
return false;
}
/* Is the source nullable and the target non-nullable?
* The source is nullable if it is nullable or if the target is generated
* for an unmatched column in an insert with a column list.
* This additional check is needed because when we generate any additional
* source RCs for an insert with a column list the generated RCs for any
* non-specified columns get the type info from the column. Thus,
* for t1(non_nullable, nullable)
* insert into t2 (nullable) values 1;
* RCType.isNullable() returns false for the generated source RC for
* non_nullable. In this case, we want to see it as
*/
if ((! resultColumnType.isNullable()) &&
(otherResultColumnType.isNullable() ||
otherColumn.isGeneratedForUnmatchedColumnInInsert()))
{
return false;
}
return true;
}
/**
* Is this a generated column?
*
* @return Boolean - whether or not this column is a generated column.
*/
boolean isGenerated()
{
return (_isGenerated == true);
}
/**
* Is this columm generated for an unmatched column in an insert?
*
* @return Boolean - whether or not this columm was generated for an unmatched column in an insert.
*/
boolean isGeneratedForUnmatchedColumnInInsert()
{
return (_isGeneratedForUnmatchedColumnInInsert == true);
}
/**
* Mark this a columm as a generated column
*/
void markGenerated()
{
_isGenerated = true;
/* A generated column is a referenced column */
_isReferenced = true;
}
/**
* Mark this a columm as generated for an unmatched column in an insert
*/
void markGeneratedForUnmatchedColumnInInsert()
{
_isGeneratedForUnmatchedColumnInInsert = true;
/* A generated column is a referenced column */
_isReferenced = true;
}
/**
* Is this a referenced column?
*
* @return Boolean - whether or not this column is a referenced column.
*/
boolean isReferenced()
{
return _isReferenced;
}
/**
* Mark this column as a referenced column.
*/
void setReferenced()
{
_isReferenced = true;
}
/**
* Mark this column as a referenced column if it is already marked as referenced or if any result column in
* its chain of virtual columns is marked as referenced.
*/
void pullVirtualIsReferenced()
{
if( isReferenced())
return;
for( ValueNode expr = _expression; expr != null && (expr instanceof VirtualColumnNode);)
{
VirtualColumnNode vcn = (VirtualColumnNode) expr;
ResultColumn src = vcn.getSourceColumn();
if( src.isReferenced())
{
setReferenced();
return;
}
expr = src.getExpression();
}
} // end of pullVirtualIsReferenced
/**
* Mark this column as an unreferenced column.
*/
void setUnreferenced()
{
_isReferenced = false;
}
/**
* Mark this RC and all RCs in the underlying
* RC/VCN chain as referenced.
*/
void markAllRCsInChainReferenced()
{
setReferenced();
ValueNode vn = _expression;
while (vn instanceof VirtualColumnNode)
{
VirtualColumnNode vcn = (VirtualColumnNode) vn;
ResultColumn rc = vcn.getSourceColumn();
rc.setReferenced();
vn = rc.getExpression();
}
}
/**
* Is this a redundant ResultColumn?
*
* @return Boolean - whether or not this RC is redundant.
*/
boolean isRedundant()
{
return _isRedundant;
}
/**
* Mark this ResultColumn as redundant.
*/
void setRedundant()
{
_isRedundant = true;
}
/**
* Mark this ResultColumn as a grouping column in the SELECT list
*/
void markAsGroupingColumn()
{
_isGroupingColumn = true;
}
/**
* Look for and reject ?/-?/+? parameter under this ResultColumn. This is
* called for SELECT statements.
*
* @exception StandardException Thrown if a ?/-?/+? parameter was found
* directly under this ResultColumn.
*/
void rejectParameter() throws StandardException
{
if ((_expression != null) && (_expression.isParameterNode()))
throw StandardException.newException(SQLState.LANG_PARAM_IN_SELECT_LIST);
}
/*
** The following methods implement the Comparable interface.
*/
public int compareTo(ResultColumn other)
{
ResultColumn otherResultColumn = other;
return this.getColumnPosition() - otherResultColumn.getColumnPosition();
}
/**
* Mark this column as being updated by an update statement.
*/
void markUpdated()
{
_updated = true;
}
/**
* Mark this column as being updatable, so we can make sure it is in the
* "for update" list of a positioned update.
*/
void markUpdatableByCursor()
{
_updatableByCursor = true;
}
/**
* Tell whether this column is being updated.
*
* @return true means this column is being updated.
*/
boolean updated()
{
return _updated;
}
/**
* Tell whether this column is updatable by a positioned update.
*
* @return true means this column is updatable
*/
@Override
public boolean updatableByCursor()
{
return _updatableByCursor;
}
/**
* Make a copy of this ResultColumn in a new ResultColumn
*
* @return A new ResultColumn with the same contents as this one
*
* @exception StandardException Thrown on error
*/
ResultColumn cloneMe() throws StandardException
{
ResultColumn newResultColumn;
ValueNode cloneExpr;
/* If expression is a ColumnReference, then we want to
* have the RC's clone have a clone of the ColumnReference
* for it's expression. This is for the special case of
* cloning the SELECT list for the HAVING clause in the parser.
* The SELECT generated for the HAVING needs its own copy
* of the ColumnReferences.
*/
if (_expression instanceof ColumnReference)
{
cloneExpr = ((ColumnReference) _expression).getClone();
}
else
{
cloneExpr = _expression;
}
/* If a columnDescriptor exists, then we must propagate it */
if (_columnDescriptor != null)
{
newResultColumn = new ResultColumn(
_columnDescriptor, _expression, getContextManager());
newResultColumn.setExpression(cloneExpr);
}
else
{
newResultColumn = new ResultColumn(
getName(), cloneExpr, getContextManager());
}
/* Set the VirtualColumnId and name in the new node */
newResultColumn.setVirtualColumnId(getVirtualColumnId());
/* Set the type and name information in the new node */
newResultColumn.setName(getName());
newResultColumn.setType(getTypeServices());
newResultColumn.setNameGenerated(isNameGenerated());
// For OFFSET/FETCH we need the also clone table name to avoid failing
// check #2 in EmbedResultSet#checksBeforeUpdateXXX. Clone schema for
// good measure...
newResultColumn.setSourceTableName(getSourceTableName());
newResultColumn.setSourceSchemaName(getSourceSchemaName());
/* Set the "is generated for unmatched column in insert" status in the new node
This if for bug 4194*/
if (isGeneratedForUnmatchedColumnInInsert())
newResultColumn.markGeneratedForUnmatchedColumnInInsert();
/* Set the "is referenced" status in the new node */
if (isReferenced())
newResultColumn.setReferenced();
/* Set the "updated" status in the new node */
if (updated())
newResultColumn.markUpdated();
/* Setthe "updatable by cursor" status in the new node */
if (updatableByCursor())
newResultColumn.markUpdatableByCursor();
if (isAutoincrementGenerated())
newResultColumn.setAutoincrementGenerated();
if (isAutoincrement())
newResultColumn.setAutoincrement();
if (isGroupingColumn())
newResultColumn.markAsGroupingColumn();
if (isRightOuterJoinUsingClause()) {
newResultColumn.setRightOuterJoinUsingClause(true);
}
if (getJoinResultSet() != null) {
newResultColumn.setJoinResultset(getJoinResultSet());
}
if (isGenerated()) {
newResultColumn.markGenerated();
}
newResultColumn.copyTagsFrom( this );
return newResultColumn;
}
/**
* Get the maximum size of the column
*
* @return the max size
*/
int getMaximumColumnSize()
{
return getTypeServices().getTypeId()
.getApproximateLengthInBytes(getTypeServices());
}
@Override
public DataTypeDescriptor getTypeServices()
{
DataTypeDescriptor type = super.getTypeServices();
if (type != null)
return type;
if (getExpression() != null)
return getExpression().getTypeServices();
return null;
}
/**
* Return the variant type for the underlying expression.
* The variant type can be:
* VARIANT - variant within a scan
* (method calls and non-static field access)
* SCAN_INVARIANT - invariant within a scan
* (column references from outer tables)
* QUERY_INVARIANT - invariant within the life of a query
* CONSTANT - constant
*
* @return The variant type for the underlying expression.
* @exception StandardException thrown on error
*/
@Override
protected int getOrderableVariantType() throws StandardException
{
/*
** If the expression is VARIANT, then
** return VARIANT. Otherwise, we return
** CONSTANT. For result columns that are
** generating autoincrement values, the result
** is variant.
*/
int expType;
if (isAutoincrementGenerated()) {
expType = Qualifier.VARIANT;
} else if (_expression != null) {
expType = _expression.getOrderableVariantType();
} else {
expType = Qualifier.CONSTANT;
}
switch (expType)
{
case Qualifier.VARIANT:
return Qualifier.VARIANT;
case Qualifier.SCAN_INVARIANT:
case Qualifier.QUERY_INVARIANT:
return Qualifier.SCAN_INVARIANT;
default:
return Qualifier.CONSTANT;
}
}
/**
* Accept the visitor for all visitable children of this node.
*
* @param v the visitor
*
* @exception StandardException on error
*/
@Override
void acceptChildren(Visitor v)
throws StandardException
{
super.acceptChildren(v);
if (_expression != null)
{
setExpression( (ValueNode)_expression.accept(v) );
}
}
/**
* Verify that this RC is orderable.
*
* @exception StandardException Thrown on error
*/
void verifyOrderable() throws StandardException
{
/*
* Do not check to see if we can map user types
* to built-in types. The ability to do so does
* not mean that ordering will work. In fact,
* as of version 2.0, ordering does not work on
* user types.
*/
if (!getTypeId().orderable(getClassFactory()))
{
throw StandardException.newException(SQLState.LANG_COLUMN_NOT_ORDERABLE_DURING_EXECUTION,
getTypeId().getSQLTypeName());
}
}
/**
If this ResultColumn is bound to a column in a table
get the column descriptor for the column in the table.
Otherwise return null.
*/
ColumnDescriptor getTableColumnDescriptor() {return _columnDescriptor;}
/**
* Returns true if this result column is a placeholder for a generated
* autoincrement value.
*/
boolean isAutoincrementGenerated()
{
return _autoincrementGenerated;
}
void setAutoincrementGenerated()
{
_autoincrementGenerated = true;
}
void resetAutoincrementGenerated()
{
_autoincrementGenerated = false;
}
public boolean isAutoincrement()
{
return _autoincrement;
}
void setAutoincrement()
{
_autoincrement = true;
}
public boolean isGroupingColumn()
{
return _isGroupingColumn;
}
/**
* @exception StandardException Thrown on error
*/
@SuppressWarnings("fallthrough")
private DataValueDescriptor convertConstant(TypeId toTypeId, int maxWidth,
DataValueDescriptor constantValue)
throws StandardException
{
int formatId = toTypeId.getTypeFormatId();
DataValueFactory dvf = getDataValueFactory();
switch (formatId)
{
default:
case StoredFormatIds.CHAR_TYPE_ID:
return constantValue;
case StoredFormatIds.VARCHAR_TYPE_ID:
String sourceValue = constantValue.getString();
int sourceWidth = sourceValue.length();
int posn;
/*
** If the input is already the right length, no normalization is
** necessary - just return the source.
**
*/
if (sourceWidth <= maxWidth)
{
if(formatId == StoredFormatIds.VARCHAR_TYPE_ID)
return dvf.getVarcharDataValue(sourceValue);
}
/*
** Check whether any non-blank characters will be truncated.
*/
for (posn = maxWidth; posn < sourceWidth; posn++)
{
if (sourceValue.charAt(posn) != ' ')
{
String typeName = null;
if (formatId == StoredFormatIds.VARCHAR_TYPE_ID)
typeName = TypeId.VARCHAR_NAME;
throw StandardException.newException(SQLState.LANG_STRING_TRUNCATION,
typeName,
StringUtil.formatForPrint(sourceValue),
String.valueOf(maxWidth));
}
}
if (formatId == StoredFormatIds.VARCHAR_TYPE_ID)
return dvf.getVarcharDataValue(sourceValue.substring(0, maxWidth));
case StoredFormatIds.LONGVARCHAR_TYPE_ID:
//No need to check widths here (unlike varchar), since no max width
return dvf.getLongvarcharDataValue(constantValue.getString());
}
}
public TableName getTableNameObject() {
return null;
}
/* Get the wrapped reference if any */
public ColumnReference getReference() { return _reference; }
/** Get the column descriptor */
ColumnDescriptor getColumnDescriptor() { return _columnDescriptor; }
/**
* Get the source BaseColumnNode for this result column. The
* BaseColumnNode cannot be found unless the ResultColumn is bound
* and is a simple reference to a column in a BaseFromTable.
*
* @return a BaseColumnNode,
* or null if a BaseColumnNode cannot be found
*/
BaseColumnNode getBaseColumnNode() {
ValueNode vn = _expression;
while (true) {
if (vn instanceof ResultColumn) {
vn = ((ResultColumn) vn)._expression;
} else if (vn instanceof ColumnReference) {
vn = ((ColumnReference) vn).getSource();
} else if (vn instanceof VirtualColumnNode) {
vn = ((VirtualColumnNode) vn).getSourceColumn();
} else if (vn instanceof BaseColumnNode) {
return (BaseColumnNode) vn;
} else {
return null;
}
}
}
/**
* Search the tree beneath this ResultColumn until we find
* the number of the table to which this RC points, and
* return that table number. If we can't determine which
* table this RC is for, then return -1.
*
* There are two places we can find the table number: 1) if
* our expression is a ColumnReference, then we can get the
* target table number from the ColumnReference and that's
* it; 2) if expression is a VirtualColumnNode, then if
* the VirtualColumnNode points to a FromBaseTable, we can
* get that FBT's table number; otherwise, we walk the
* VirtualColumnNode-ResultColumn chain and do a recursive
* search.
*
* @return The number of the table to which this ResultColumn
* points, or -1 if we can't determine that from where we are.
*/
int getTableNumber()
throws StandardException
{
if (_expression instanceof ColumnReference)
return ((ColumnReference)_expression).getTableNumber();
else if (_expression instanceof VirtualColumnNode)
{
VirtualColumnNode vcn = (VirtualColumnNode)_expression;
// If the VCN points to a FromBaseTable, just get that
// table's number.
if (vcn.getSourceResultSet() instanceof FromBaseTable)
{
return ((FromBaseTable)vcn.getSourceResultSet()).
getTableNumber();
}
// Else recurse down the VCN.
return vcn.getSourceColumn().getTableNumber();
}
// We can get here if expression has neither a column
// reference nor a FromBaseTable beneath it--for example,
// if it is of type BaseColumnNode.
return -1;
}
boolean isEquivalent(ValueNode o) throws StandardException
{
if (isSameNodeKind(o)) {
ResultColumn other = (ResultColumn)o;
if (_expression != null) {
return _expression.isEquivalent(other._expression);
}
}
return false;
}
/**
* Return true if this result column represents a generated column.
*/
public boolean hasGenerationClause()
{
if ( (_columnDescriptor != null) && _columnDescriptor.hasGenerationClause() ) { return true; }
else { return false; }
}
}