blob: 7656cd2a14149caec0d2c2933b24a4f60177c73b [file] [log] [blame]
/*
Derby - Class org.apache.derby.impl.sql.compile.CastNode
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.lang.reflect.Modifier;
import java.sql.Types;
import java.util.List;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.reference.ClassName;
import org.apache.derby.iapi.reference.Limits;
import org.apache.derby.iapi.reference.SQLState;
import org.apache.derby.iapi.services.classfile.VMOpcode;
import org.apache.derby.iapi.services.compiler.LocalField;
import org.apache.derby.iapi.services.compiler.MethodBuilder;
import org.apache.derby.iapi.services.context.ContextManager;
import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.sql.compile.TypeCompiler;
import org.apache.derby.iapi.sql.compile.Visitor;
import org.apache.derby.iapi.types.DataTypeDescriptor;
import org.apache.derby.iapi.types.DataTypeUtilities;
import org.apache.derby.iapi.types.DataValueDescriptor;
import org.apache.derby.iapi.types.NumberDataType;
import org.apache.derby.iapi.types.TypeId;
import org.apache.derby.iapi.util.JBitSet;
import org.apache.derby.iapi.util.StringUtil;
/**
* An CastNode represents a cast expression.
*
*/
class CastNode extends ValueNode
{
ValueNode castOperand;
private int targetCharType;
TypeId sourceCTI = null;
private boolean forDataTypeFunction = false;
/** The original, unbound descriptor for the target type, if it is a UDT. */
private DataTypeDescriptor targetUDT;
/** This variable gets set by the parser to indicate that this CAST node
* has been generated by the parser. This means that we should use the
* collation info of the current compilation schema for this node's
* collation setting. If this variable does not get set to true, then it
* means that this CAST node has been an internally generated node and we
* should not touch the collation info set for this CAST node because it
* has been already set correctly by the class that generated this CAST
* node. Collation info is part of the DataTypeDescriptor that's defined
* on the ValueNode (the super class of this CastNode class)
*/
private boolean externallyGeneratedCastNode = false;
/*
** Static array of valid casts. Dimentions
** produce a single boolean which indicates
** whether the case is possible or not.
*/
/**
* Method calls:
* Argument type has the same semantics as assignment:
* Section 9.2 (Store assignment). There, General Rule
* 2.b.v.2 says that the database should raise an exception
* if truncation occurs when stuffing a string value into a
* VARCHAR, so make sure CAST doesn't issue warning only.
*/
private boolean assignmentSemantics = false;
/**
* The name of the target type if it's a UDT. It is partly redundant, as
* the name can also be retrieved from the type descriptor. Additionally,
* it contains information about the location of the UDT name in the
* query text, which is useful if the query text needs to be rewritten.
* (Useful for example when rewriting a CHECK constraint definition to
* have fully qualified names before storing it in the dictionary.) This
* field is only set for <b>explicit</b> casts to a UDT.
*/
private TableName udtTargetName;
/**
* Constructor for a CastNode
*
* @param castOperand The operand of the node
* @param castTarget DataTypeServices (target type of cast)
* @param cm The context manager
*
* @exception StandardException Thrown on error
*/
CastNode(ValueNode castOperand,
DataTypeDescriptor castTarget,
ContextManager cm) throws StandardException {
super(cm);
this.castOperand = castOperand;
// DERBY-6421: setType() tries to bind user defined types. We don't
// want to do any binding here, since we could be called during
// parsing. If the target type is a UDT, just store it for now and
// do the binding later when bindExpression() or bindCastNodeOnly()
// is called.
if (castTarget.getTypeId().isUserDefinedTypeId()) {
targetUDT = castTarget;
} else {
setType(castTarget);
}
}
/**
* Constructor for a CastNode
*
* @param castOperand The operand of the node
* @param charType CHAR or VARCHAR JDBC type as target
* @param charLength target type length
* @param cm The context manager
*
* @exception StandardException Thrown on error
*/
CastNode(ValueNode castOperand,
int charType,
int charLength,
ContextManager cm) throws StandardException {
super(cm);
this.castOperand = castOperand;
int charLen = charLength;
targetCharType = charType;
if (charLen < 0) // unknown, figure out later
return;
setType(DataTypeDescriptor.getBuiltInDataTypeDescriptor(targetCharType, charLen));
}
/**
* 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 "castTarget: " + getTypeServices() + "\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 (castOperand != null)
{
printLabel(depth, "castOperand: ");
castOperand.treePrint(depth + 1);
}
}
}
protected int getOrderableVariantType() throws StandardException
{
return castOperand.getOrderableVariantType();
}
/**
* Bind this expression. This means binding the sub-expressions,
* as well as figuring out what the return type is for this expression.
*
* @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 @SuppressWarnings("fallthrough")
ValueNode bindExpression(FromList fromList, SubqueryList subqueryList, List<AggregateNode> aggregates)
throws StandardException
{
castOperand = castOperand.bindExpression(
fromList, subqueryList,
aggregates);
// Bind the target UDT.
if (targetUDT != null) {
setType(targetUDT);
}
if (getTypeServices() == null) //CHAR or VARCHAR function without specifying target length
{
DataTypeDescriptor opndType = castOperand.getTypeServices();
int length = -1;
TypeId srcTypeId = opndType.getTypeId();
if (srcTypeId.isNumericTypeId())
{
length = opndType.getPrecision() + 1; // 1 for the sign
if (opndType.getScale() > 0)
length += 1; // 1 for the decimal .
}
/*
* Derby-1132 : The length for the target type was calculated
* incorrectly while Char & Varchar functions were used. Thus
* adding the check for Char & Varchar and calculating the
* length based on the operand type.
*/
else if(srcTypeId.isStringTypeId())
{
length = opndType.getMaximumWidth();
// Truncate the target type width to the max width of the
// data type
if (this.targetCharType == Types.CHAR)
length = Math.min(length, Limits.DB2_CHAR_MAXWIDTH);
else if (this.targetCharType == Types.VARCHAR)
length = Math.min(length, Limits.DB2_VARCHAR_MAXWIDTH);
}
else
{
TypeId typeid = opndType.getTypeId();
if (length < 0) {
length = DataTypeUtilities.getColumnDisplaySize(
typeid.getJDBCTypeId(), -1);
}
}
if (length < 0)
length = 1; // same default as in parser
setType(DataTypeDescriptor.getBuiltInDataTypeDescriptor(targetCharType, length));
}
/*
** If castOperand is an untyped null,
** then we must set the type.
*/
if (castOperand instanceof UntypedNullConstantNode)
{
castOperand.setType(getTypeServices());
}
bindCastNodeOnly();
/* We can't chop out cast above an untyped null because
* the store can't handle it.
*/
if ((castOperand instanceof ConstantNode) &&
!(castOperand instanceof UntypedNullConstantNode))
{
/* If the castOperand is a typed constant then we do the cast at
* bind time and return a constant of the correct type.
* NOTE: This could return an exception, but we're prepared to
* deal with that. (NumberFormatException, etc.)
* We only worry about the easy (and useful)
* converions at bind time.
* Here's what we support:
* source destination
* ------ -----------
* boolean boolean
* boolean char
* char boolean
* char date/time/ts
* char non-decimal numeric
* date/time/ts char
* numeric char
* numeric non-decimal numeric
*/
/* RESOLVE - to be filled in. */
ValueNode retNode = this;
int sourceJDBCTypeId = sourceCTI.getJDBCTypeId();
int destJDBCTypeId = getTypeId().getJDBCTypeId();
switch (sourceJDBCTypeId)
{
case Types.BIT:
case Types.BOOLEAN:
// (BIT is boolean)
if (destJDBCTypeId == Types.BIT || destJDBCTypeId == Types.BOOLEAN)
{
retNode = castOperand;
}
else if (destJDBCTypeId == Types.CHAR)
{
BooleanConstantNode bcn = (BooleanConstantNode) castOperand;
String booleanString = bcn.getValueAsString();
retNode = new CharConstantNode(
booleanString,
getTypeServices().getMaximumWidth(),
getContextManager());
}
break;
case Types.CHAR:
retNode = getCastFromCharConstant(destJDBCTypeId);
break;
case Types.DATE:
case Types.TIME:
case Types.TIMESTAMP:
if (destJDBCTypeId == Types.CHAR)
{
String castValue =
((UserTypeConstantNode) castOperand).
getObjectValue().
toString();
retNode = new CharConstantNode(
castValue,
getTypeServices().getMaximumWidth(),
getContextManager());
}
break;
case Types.DECIMAL:
// ignore decimal -> decimal casts for now
if (destJDBCTypeId == Types.DECIMAL ||
destJDBCTypeId == Types.NUMERIC)
break;
// fall through
case Types.TINYINT:
case Types.SMALLINT:
case Types.INTEGER:
case Types.BIGINT:
case Types.DOUBLE:
case Types.REAL:
retNode = getCastFromNumericType(
((ConstantNode) castOperand).getValue(),
destJDBCTypeId);
break;
}
// Return the new constant if the cast was performed
return retNode;
}
return this;
}
/**
* Bind this node but not its child. Caller has already bound
* the child.
* This is useful for when we generate a CastNode during binding
* after having already bound the child.
*
* @exception StandardException Thrown on error
*/
void bindCastNodeOnly()
throws StandardException
{
// Bind the target UDT.
if (targetUDT != null) {
setType(targetUDT);
}
/*
** The result type is always castTarget.
*/
sourceCTI = castOperand.getTypeId();
//If the result type of cast is string data type, then that data type
//should get it's collation type from the current schema.
if (externallyGeneratedCastNode && getTypeId().isStringTypeId()) {
//set the collation type to be same as the compilation schema's
//collation type. Collation derivation will be set to "IMPLICIT".
setCollationUsingCompilationSchema();
}
/*
** If it is a java cast, do some work to make sure
** the classes are ok and that they are compatible
*/
if (getTypeId().userType())
{
setType( bindUserType( getTypeServices() ) );
String className = getTypeId().getCorrespondingJavaTypeName();
verifyClassExist(className);
}
// Set the schema name of the UDT target type.
if (udtTargetName != null) {
udtTargetName.setSchemaName(
getTypeId().getBaseTypeId().getSchemaName());
}
// Obviously the type of a parameter that
// requires its type from context (a parameter)
// gets its type from the type of the CAST.
if (castOperand.requiresTypeFromContext())
{
castOperand.setType(getTypeServices());
}
/*
** If it isn't null, then we have
** a cast from one JBMS type to another. So we
** have to figure out if it is legit.
*/
else if (!(castOperand instanceof UntypedNullConstantNode))
{
/*
** Make sure we can assign the two classes
*/
TypeCompiler tc = castOperand.getTypeCompiler();
if (! tc.convertible(getTypeId(), forDataTypeFunction))
{
throw StandardException.newException(SQLState.LANG_INVALID_CAST,
sourceCTI.getSQLTypeName(),
getTypeId().getSQLTypeName());
}
}
//
// Preserve the nullability of the operand since a CAST
// of a non-NULL value is also non-NULL. However, if the source type is
// a non-nullable string type and the target type is a boolean, then the result
// still must be nullable because the string "unknown" casts to boolean NULL.
//
if (
castOperand.getTypeServices().getTypeId().isStringTypeId() &&
getTypeId().isBooleanTypeId()
)
{ setNullability( true ); }
else { setNullability(castOperand.getTypeServices().isNullable()); }
if (targetUDT != null)
{
addUDTUsagePriv( this );
}
}
/**
* Get a constant representing the cast from a CHAR to another
* type. If this is not an "easy" cast to perform, then just
* return this cast node.
* Here's what we think is "easy":
* source destination
* ------ -----------
* char boolean
* char date/time/ts
* char non-decimal numeric
*
* @param destJDBCTypeId The destination JDBC TypeId
*
* @return The new top of the tree (this CastNode or a new Constant)
*
* @exception StandardException Thrown on error
*/
private ValueNode getCastFromCharConstant(int destJDBCTypeId)
throws StandardException
{
String charValue = ((CharConstantNode) castOperand).getString();
String cleanCharValue = StringUtil.SQLToUpperCase(charValue.trim());
ValueNode retNode = this;
switch (destJDBCTypeId)
{
case Types.BIT:
case Types.BOOLEAN:
if (cleanCharValue.equals("TRUE"))
{
return new BooleanConstantNode(true, getContextManager());
}
else if (cleanCharValue.equals("FALSE"))
{
return new BooleanConstantNode(false, getContextManager());
}
else if (cleanCharValue.equals("UNKNOWN"))
{
return new BooleanConstantNode(getContextManager());
}
else
{
throw StandardException.newException(SQLState.LANG_FORMAT_EXCEPTION, "boolean");
}
case Types.DATE:
return new UserTypeConstantNode(
getDataValueFactory().getDateValue(cleanCharValue, false),
getContextManager());
case Types.TIMESTAMP:
return new UserTypeConstantNode(
getDataValueFactory().getTimestampValue(cleanCharValue, false),
getContextManager());
case Types.TIME:
return new UserTypeConstantNode(
getDataValueFactory().getTimeValue(cleanCharValue, false),
getContextManager());
case Types.TINYINT:
case Types.SMALLINT:
case Types.INTEGER:
case Types.BIGINT:
try
{
// #3756 - Truncate decimal portion for casts to integer
return getCastFromIntegralType((long) Double.parseDouble(cleanCharValue),
destJDBCTypeId);
}
catch (NumberFormatException nfe)
{
String sqlName = TypeId.getBuiltInTypeId(destJDBCTypeId).getSQLTypeName();
throw StandardException.newException(SQLState.LANG_FORMAT_EXCEPTION, sqlName);
}
case Types.REAL:
Float floatValue;
try
{
floatValue = Float.valueOf(cleanCharValue);
}
catch (NumberFormatException nfe)
{
throw StandardException.newException(SQLState.LANG_FORMAT_EXCEPTION, "float");
}
return new NumericConstantNode(
TypeId.getBuiltInTypeId(Types.REAL),
floatValue,
getContextManager());
case Types.DOUBLE:
Double doubleValue;
try
{
doubleValue = Double.parseDouble(cleanCharValue);
}
catch (NumberFormatException nfe)
{
throw StandardException.newException(SQLState.LANG_FORMAT_EXCEPTION, "double");
}
return new NumericConstantNode(
TypeId.getBuiltInTypeId(Types.DOUBLE),
doubleValue,
getContextManager());
}
return retNode;
}
/**
* Get a constant representing the cast from an integral type to another
* type. If this is not an "easy" cast to perform, then just
* return this cast node.
* Here's what we think is "easy":
* source destination
* ------ -----------
* integral type non-decimal numeric
* integral type char
*
* @param longValue integral type as a long to cast from
* @param destJDBCTypeId The destination JDBC TypeId
*
* @return The new top of the tree (this CastNode or a new Constant)
*
* @exception StandardException Thrown on error
*/
private ValueNode getCastFromIntegralType(
long longValue,
int destJDBCTypeId)
throws StandardException
{
ValueNode retNode = this;
switch (destJDBCTypeId)
{
case Types.CHAR:
return new CharConstantNode(
Long.toString(longValue),
getTypeServices().getMaximumWidth(),
getContextManager());
case Types.TINYINT:
if (longValue < Byte.MIN_VALUE ||
longValue > Byte.MAX_VALUE)
{
throw StandardException.newException(SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "TINYINT");
}
return new NumericConstantNode(
TypeId.getBuiltInTypeId(Types.TINYINT),
(byte) longValue,
getContextManager());
case Types.SMALLINT:
if (longValue < Short.MIN_VALUE ||
longValue > Short.MAX_VALUE)
{
throw StandardException.newException(SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "SHORT");
}
return new NumericConstantNode(
TypeId.getBuiltInTypeId(destJDBCTypeId),
(short) longValue,
getContextManager());
case Types.INTEGER:
if (longValue < Integer.MIN_VALUE ||
longValue > Integer.MAX_VALUE)
{
throw StandardException.newException(SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "INTEGER");
}
return new NumericConstantNode(
TypeId.getBuiltInTypeId(destJDBCTypeId),
(int) longValue,
getContextManager());
case Types.BIGINT:
return new NumericConstantNode(
TypeId.getBuiltInTypeId(destJDBCTypeId),
longValue,
getContextManager());
case Types.REAL:
if (Math.abs(longValue) > Float.MAX_VALUE)
{
throw StandardException.newException(SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "REAL");
}
return new NumericConstantNode(
TypeId.getBuiltInTypeId(destJDBCTypeId),
(float) longValue,
getContextManager());
case Types.DOUBLE:
return new NumericConstantNode(
TypeId.getBuiltInTypeId(destJDBCTypeId),
(double) longValue,
getContextManager());
}
return retNode;
}
/**
* Get a constant representing the cast from a non-integral type to another
* type. If this is not an "easy" cast to perform, then just
* return this cast node.
* Here's what we think is "easy":
* source destination
* ------ -----------
* non-integral type non-decimal numeric
* non-integral type char
*
* @param constantValue non-integral type a a double to cast from
* @param destJDBCTypeId The destination JDBC TypeId
*
* @return The new top of the tree (this CastNode or a new Constant)
*
* @exception StandardException Thrown on error
*/
private ValueNode getCastFromNumericType(
DataValueDescriptor constantValue,
int destJDBCTypeId)
throws StandardException
{
switch (destJDBCTypeId)
{
case Types.CHAR:
return new CharConstantNode(
constantValue.getString(),
getTypeServices().getMaximumWidth(),
getContextManager());
case Types.TINYINT:
return new NumericConstantNode(
TypeId.getBuiltInTypeId(destJDBCTypeId),
Byte.valueOf(constantValue.getByte()),
getContextManager());
case Types.SMALLINT:
return new NumericConstantNode(
TypeId.getBuiltInTypeId(destJDBCTypeId),
Short.valueOf(constantValue.getShort()),
getContextManager());
case Types.INTEGER:
return new NumericConstantNode(
TypeId.getBuiltInTypeId(destJDBCTypeId),
Integer.valueOf(constantValue.getInt()),
getContextManager());
case Types.BIGINT:
return new NumericConstantNode(
TypeId.getBuiltInTypeId(destJDBCTypeId),
Long.valueOf(constantValue.getLong()),
getContextManager());
case Types.REAL:
return new NumericConstantNode(
TypeId.getBuiltInTypeId(destJDBCTypeId),
Float.valueOf(NumberDataType.normalizeREAL(
constantValue.getDouble())),
getContextManager());
case Types.DOUBLE:
// no need to normalize here because no constant could be out of range for a double
return new NumericConstantNode(
TypeId.getBuiltInTypeId(destJDBCTypeId),
Double.valueOf(constantValue.getDouble()),
getContextManager());
}
return this;
}
/**
* 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
ValueNode preprocess(int numTables,
FromList outerFromList,
SubqueryList outerSubqueryList,
PredicateList outerPredicateList)
throws StandardException
{
castOperand = castOperand.preprocess(numTables,
outerFromList, outerSubqueryList,
outerPredicateList);
return this;
}
/**
* Categorize this predicate. Initially, this means
* building a bit map of the referenced tables for each predicate.
* If the source of this ColumnReference (at the next underlying level)
* is not a ColumnReference or a VirtualColumnNode then this predicate
* will not be pushed down.
*
* For example, in:
* select * from (select 1 from s) a (x) where x = 1
* we will not push down x = 1.
* NOTE: It would be easy to handle the case of a constant, but if the
* inner SELECT returns an arbitrary expression, then we would have to copy
* that tree into the pushed predicate, and that tree could contain
* subqueries and method calls.
* RESOLVE - revisit this issue once we have views.
*
* @param referencedTabs JBitSet with bit map of referenced FromTables
* @param simplePredsOnly Whether or not to consider method
* calls, field references and conditional nodes
* when building bit map
*
* @return boolean Whether or not source.expression is a ColumnReference
* or a VirtualColumnNode.
*
* @exception StandardException Thrown on error
*/
@Override
boolean categorize(JBitSet referencedTabs, boolean simplePredsOnly)
throws StandardException
{
return castOperand.categorize(referencedTabs, simplePredsOnly);
}
/**
* Remap all ColumnReferences in this tree to be clones of the
* underlying expression.
*
* @return ValueNode The remapped expression tree.
*
* @exception StandardException Thrown on error
*/
@Override
ValueNode remapColumnReferencesToExpressions()
throws StandardException
{
castOperand = castOperand.remapColumnReferencesToExpressions();
return this;
}
/**
* Return whether or not this expression tree represents a constant expression.
*
* @return Whether or not this expression tree represents a constant expression.
*/
@Override
boolean isConstantExpression()
{
return castOperand.isConstantExpression();
}
/** @see ValueNode#constantExpression */
@Override
boolean constantExpression(PredicateList whereClause)
{
return castOperand.constantExpression(whereClause);
}
/**
* Return an Object representing the bind time value of this
* expression tree. If the expression tree does not evaluate to
* a constant at bind time then we return null.
* This is useful for bind time resolution of VTIs.
* RESOLVE: What do we do for primitives?
*
* @return An Object representing the bind time value of this expression tree.
* (null if not a bind time constant.)
*
* @exception StandardException Thrown on error
*/
@Override
Object getConstantValueAsObject()
throws StandardException
{
Object sourceObject = castOperand.getConstantValueAsObject();
// RESOLVE - need to figure out how to handle casts
if (sourceObject == null)
{
return null;
}
// Simple if source and destination are of same type
if (sourceCTI.getCorrespondingJavaTypeName().equals(
getTypeId().getCorrespondingJavaTypeName()))
{
return sourceObject;
}
// RESOLVE - simply return null until we can figure out how to
// do the cast
return null;
}
/**
* Do code generation for this unary operator.
*
* @param acb The ExpressionClassBuilder for the class we're generating
* @param mb The method the code to place the code
*
* @exception StandardException Thrown on error
*/
@Override
void generateExpression(ExpressionClassBuilder acb, MethodBuilder mb)
throws StandardException
{
castOperand.generateExpression(acb, mb);
/* No need to generate code for null constants */
if (castOperand instanceof UntypedNullConstantNode)
{
return;
}
/* HACK ALERT. When casting a parameter, there
* is not sourceCTI. Code generation requires one,
* so we simply set it to be the same as the
* destCTI. The user can still pass whatever
* type they'd like in as a parameter.
* They'll get an exception, as expected, if the
* conversion cannot be performed.
*/
else if (castOperand.requiresTypeFromContext())
{
sourceCTI = getTypeId();
}
genDataValueConversion(acb, mb);
}
private void genDataValueConversion(ExpressionClassBuilder acb,
MethodBuilder mb)
throws StandardException
{
MethodBuilder acbConstructor = acb.getConstructor();
String resultTypeName = getTypeCompiler().interfaceName();
/* field = method call */
/* Allocate an object for re-use to hold the result of the operator */
LocalField field = acb.newFieldDeclaration(Modifier.PRIVATE, resultTypeName);
/*
** Store the result of the method call in the field, so we can re-use
** the object.
*/
acb.generateNull(acbConstructor, getTypeCompiler(getTypeId()),
getTypeServices().getCollationType());
acbConstructor.setField(field);
/*
For most types generate
targetDVD.setValue(sourceDVD);
For source or destination java types generate
Object o = sourceDVD.getObject();
targetDVD.setObjectForCast(o, o instanceof dest java type, dest java type);
// optional for variable length types
targetDVD.setWidth();
*/
if (!sourceCTI.userType() && !getTypeId().userType()) {
mb.getField(field); // targetDVD reference for the setValue method call
mb.swap();
mb.upCast(ClassName.DataValueDescriptor);
mb.callMethod(VMOpcode.INVOKEINTERFACE, ClassName.DataValueDescriptor,
"setValue", "void", 1);
}
else
{
/*
** generate: expr.getObject()
*/
mb.callMethod(VMOpcode.INVOKEINTERFACE, ClassName.DataValueDescriptor,
"getObject", "java.lang.Object", 0);
//castExpr
mb.getField(field); // instance for the setValue/setObjectForCast method call
mb.swap(); // push it before the value
/*
** We are casting a java type, generate:
**
** DataValueDescriptor.setObjectForCast(java.lang.Object castExpr, boolean instanceOfExpr, destinationClassName)
** where instanceOfExpr is "source instanceof destinationClass".
**
*/
String destinationType = getTypeId().getCorrespondingJavaTypeName();
// at this point method instance and cast result are on the stack
// we duplicate the cast value in order to perform the instanceof check
mb.dup();
mb.isInstanceOf(destinationType);
mb.push(destinationType);
mb.callMethod(VMOpcode.INVOKEINTERFACE, ClassName.DataValueDescriptor,
"setObjectForCast", "void", 3);
}
mb.getField(field);
/*
** If we are casting to a variable length datatype, we
** have to make sure we have set it to the correct
** length.
*/
if (getTypeId().variableLength())
{
boolean isNumber = getTypeId().isNumericTypeId();
// to leave the DataValueDescriptor value on the stack, since setWidth is void
mb.dup();
/* setWidth() is on VSDV - upcast since
* decimal implements subinterface
* of VSDV.
*/
mb.push(isNumber ? getTypeServices().getPrecision() : getTypeServices().getMaximumWidth());
mb.push(getTypeServices().getScale());
mb.push(!sourceCTI.variableLength() ||
isNumber ||
assignmentSemantics);
mb.callMethod(VMOpcode.INVOKEINTERFACE, ClassName.VariableSizeDataValue,
"setWidth", "void", 3);
}
}
/**
* 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 (castOperand != null)
{
castOperand = (ValueNode)castOperand.accept(v);
}
if (udtTargetName != null)
{
udtTargetName = (TableName) udtTargetName.accept(v);
}
}
/** This method gets called by the parser to indiciate that this CAST node
* has been generated by the parser. This means that we should use the
* collation info of the current compilation schmea for this node's
* collation setting. If this method does not get called, then it means
* that this CAST node has been an internally generated node and we should
* not touch the collation of this CAST node because it has been already
* set correctly by the class that generated this CAST node.
*/
void setForExternallyGeneratedCASTnode()
{
externallyGeneratedCastNode = true;
}
/** set this to be a dataTypeScalarFunction
*
* @param b true to use function conversion rules
*/
void setForDataTypeFunction(boolean b)
{
forDataTypeFunction = b;
}
/**
* Set assignmentSemantics to true. Used by method calls for casting actual
* arguments
*/
void setAssignmentSemantics()
{
assignmentSemantics = true;
}
/**
* {@inheritDoc}
* @throws StandardException
*/
boolean isEquivalent(ValueNode o) throws StandardException
{
if (isSameNodeKind(o)) {
CastNode other = (CastNode)o;
return getTypeServices().equals(other.getTypeServices())
&& castOperand.isEquivalent(other.castOperand);
}
return false;
}
/**
* Set the target type name if this is a cast to a UDT.
* @param name the name of the target type
*/
void setTargetUDTName(TableName name) {
udtTargetName = name;
}
}