/* Copyright (c) 2001-2019, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package org.hsqldb;

import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.lib.HsqlList;
import org.hsqldb.types.CharacterType;
import org.hsqldb.types.Type;
import org.hsqldb.types.Types;

Implementation of arithmetic and concatenation operations
Author:Campbell Burnet (campbell-burnet@users dot sourceforge.net), Fred Toussi (fredt@users dot sourceforge.net)
Version:2.3.5
Since:1.9.0
/** * Implementation of arithmetic and concatenation operations * * @author Campbell Burnet (campbell-burnet@users dot sourceforge.net) * @author Fred Toussi (fredt@users dot sourceforge.net) * @version 2.3.5 * @since 1.9.0 */
public class ExpressionArithmetic extends Expression { ExpressionArithmetic(int type, Expression left, Expression right) { super(type); nodes = new Expression[BINARY]; nodes[LEFT] = left; nodes[RIGHT] = right; switch (opType) { case OpTypes.ADD : case OpTypes.SUBTRACT : case OpTypes.MULTIPLY : case OpTypes.DIVIDE : case OpTypes.CONCAT : return; default : throw Error.runtimeError(ErrorCode.U_S0500, "Expression"); } } ExpressionArithmetic(int type, Expression e) { super(type); nodes = new Expression[UNARY]; nodes[LEFT] = e; switch (opType) { case OpTypes.NEGATE : return; default : throw Error.runtimeError(ErrorCode.U_S0500, "Expression"); } } public String getSQL() { StringBuilder sb = new StringBuilder(64); switch (opType) { case OpTypes.VALUE : if (valueData == null) { return Tokens.T_NULL; } if (dataType == null) { throw Error.runtimeError(ErrorCode.U_S0500, "Expression"); } return dataType.convertToSQLString(valueData); } String left = getContextSQL(nodes.length > 0 ? nodes[LEFT] : null); String right = getContextSQL(nodes.length > 1 ? nodes[RIGHT] : null); switch (opType) { case OpTypes.CAST : sb.append(' ').append(Tokens.T_CAST).append('('); sb.append(left).append(' ').append(Tokens.T_AS).append(' '); sb.append(dataType.getTypeDefinition()); sb.append(')'); break; case OpTypes.NEGATE : sb.append('-').append(left); break; case OpTypes.ADD : sb.append(left).append('+').append(right); break; case OpTypes.SUBTRACT : sb.append(left).append('-').append(right); break; case OpTypes.MULTIPLY : sb.append(left).append('*').append(right); break; case OpTypes.DIVIDE : sb.append(left).append('/').append(right); break; case OpTypes.CONCAT : sb.append(left).append("||").append(right); break; default : throw Error.runtimeError(ErrorCode.U_S0500, "Expression"); } return sb.toString(); } protected String describe(Session session, int blanks) { StringBuilder sb = new StringBuilder(64); sb.append('\n'); for (int i = 0; i < blanks; i++) { sb.append(' '); } switch (opType) { case OpTypes.VALUE : sb.append("VALUE = ").append( dataType.convertToSQLString(valueData)); sb.append(", TYPE = ").append(dataType.getNameString()); return sb.toString(); case OpTypes.ROW : // case OpTypes.VALUELIST : sb.append("VALUELIST "); sb.append(" TYPE = ").append(dataType.getNameString()); for (int i = 0; i < nodes.length; i++) { sb.append(nodes[i].describe(session, blanks + blanks)); sb.append(' '); } break; case OpTypes.NEGATE : sb.append("NEGATE "); break; case OpTypes.ADD : sb.append("ADD "); break; case OpTypes.SUBTRACT : sb.append("SUBTRACT "); break; case OpTypes.MULTIPLY : sb.append("MULTIPLY "); break; case OpTypes.DIVIDE : sb.append("DIVIDE "); break; case OpTypes.CONCAT : sb.append("CONCAT "); break; case OpTypes.CAST : sb.append("CAST "); sb.append(dataType.getTypeDefinition()); sb.append(' '); break; default : } if (getLeftNode() != null) { sb.append(" arg_left=["); sb.append(nodes[LEFT].describe(session, blanks + 1)); sb.append(']'); } if (getRightNode() != null) { sb.append(" arg_right=["); sb.append(nodes[RIGHT].describe(session, blanks + 1)); sb.append(']'); } return sb.toString(); } public HsqlList resolveColumnReferences(Session session, RangeGroup rangeGroup, int rangeCount, RangeGroup[] rangeGroups, HsqlList unresolvedSet, boolean acceptsSequences) { if (opType == OpTypes.VALUE) { return unresolvedSet; } for (int i = 0; i < nodes.length; i++) { if (nodes[i] == null) { continue; } unresolvedSet = nodes[i].resolveColumnReferences(session, rangeGroup, rangeCount, rangeGroups, unresolvedSet, acceptsSequences); } return unresolvedSet; } public void resolveTypes(Session session, Expression parent) { for (int i = 0; i < nodes.length; i++) { if (nodes[i] != null) { nodes[i].resolveTypes(session, this); } } switch (opType) { case OpTypes.VALUE : break; case OpTypes.NEGATE : if (nodes[LEFT].isUnresolvedParam() || nodes[LEFT].dataType == null) { throw Error.error(ErrorCode.X_42567); } dataType = nodes[LEFT].dataType; if (!dataType.isNumberType()) { throw Error.error(ErrorCode.X_42563); } if (nodes[LEFT].opType == OpTypes.VALUE) { setAsConstantValue(session, parent); } break; case OpTypes.ADD : // special case for concat using + if ((nodes[LEFT].dataType != null && nodes[LEFT].dataType .isCharacterType()) || (nodes[RIGHT].dataType != null && nodes[RIGHT].dataType .isCharacterType())) { opType = OpTypes.CONCAT; resolveTypesForConcat(session, parent); break; } // fall through case OpTypes.SUBTRACT : case OpTypes.MULTIPLY : case OpTypes.DIVIDE : resolveTypesForArithmetic(session, parent); break; case OpTypes.CONCAT : resolveTypesForConcat(session, parent); break; default : throw Error.runtimeError(ErrorCode.U_S0500, "Expression"); } } void resolveTypesForArithmetic(Session session, Expression parent) { if (nodes[LEFT].isUnresolvedParam() && nodes[RIGHT].isUnresolvedParam()) { if (parent instanceof ExpressionLogical) { Expression e = parent.nodes[LEFT]; if (e == this) { e = parent.nodes[RIGHT]; } if (e.dataType != null) { if (e.dataType.isDateOrTimestampType()) { nodes[LEFT].dataType = e.dataType; } } } } if (nodes[LEFT].isUnresolvedParam() && nodes[RIGHT].isUnresolvedParam()) { nodes[LEFT].dataType = Type.SQL_INTEGER; nodes[RIGHT].dataType = Type.SQL_INTEGER; } if (nodes[LEFT].dataType == null && nodes[RIGHT].dataType == null) { nodes[LEFT].dataType = Type.SQL_INTEGER; nodes[RIGHT].dataType = Type.SQL_INTEGER; } if (nodes[LEFT].isUnresolvedParam()) { if (nodes[RIGHT].dataType == null) { throw Error.error(ErrorCode.X_42567); } if (nodes[RIGHT].dataType.isIntervalType()) { if (parent != null) { switch (parent.opType) { case OpTypes.EQUAL : case OpTypes.GREATER_EQUAL : case OpTypes.SMALLER_EQUAL : case OpTypes.SMALLER : case OpTypes.GREATER : for (int i = 0; i < parent.nodes.length; i++) { if (parent.nodes[i] != this) { if (parent.nodes[i].dataType != null && parent.nodes[i].dataType .isDateTimeType()) { nodes[LEFT].dataType = parent.nodes[i].dataType; } break; } } break; default : } } } if (nodes[LEFT].dataType == null) { switch (opType) { case OpTypes.SUBTRACT : if (nodes[RIGHT].dataType.isIntervalType()) { nodes[LEFT].dataType = Type.SQL_TIMESTAMP_WITH_TIME_ZONE; } break; case OpTypes.ADD : if (nodes[RIGHT].dataType.isDateTimeType()) { if (nodes[RIGHT].dataType.typeComparisonGroup == Types.SQL_DATE) { nodes[LEFT].dataType = Type .SQL_INTERVAL_YEAR_TO_MONTH_MAX_PRECISION; } else { nodes[LEFT].dataType = Type .SQL_INTERVAL_DAY_TO_SECOND_MAX_PRECISION; } } else if (nodes[RIGHT].dataType.isIntervalType()) { nodes[LEFT].dataType = Type.SQL_TIMESTAMP_WITH_TIME_ZONE; } break; default : } } if (nodes[LEFT].dataType == null) { nodes[LEFT].dataType = nodes[RIGHT].dataType; } } else if (nodes[RIGHT].isUnresolvedParam()) { if (nodes[LEFT].dataType == null) { throw Error.error(ErrorCode.X_42567); } switch (opType) { case OpTypes.MULTIPLY : case OpTypes.DIVIDE : if (nodes[LEFT].dataType.isIntervalType()) { nodes[RIGHT].dataType = Type.SQL_DECIMAL; } else { nodes[RIGHT].dataType = nodes[LEFT].dataType; } break; case OpTypes.SUBTRACT : case OpTypes.ADD : if (nodes[LEFT].dataType.isDateTimeType()) { // datetime subtract - type predetermined if (dataType != null && dataType.isIntervalType()) { nodes[RIGHT].dataType = nodes[LEFT].dataType; } else if (nodes[LEFT].dataType.typeComparisonGroup == Types.SQL_DATE) { nodes[RIGHT].dataType = Type.SQL_INTERVAL_YEAR_TO_MONTH_MAX_PRECISION; } else { nodes[RIGHT].dataType = Type.SQL_INTERVAL_DAY_TO_SECOND_MAX_PRECISION; } } else { nodes[RIGHT].dataType = nodes[LEFT].dataType; } break; } } if (nodes[LEFT].dataType == null || nodes[RIGHT].dataType == null) { throw Error.error(ErrorCode.X_42567); } if (opType == OpTypes.SUBTRACT) { if (nodes[LEFT].dataType.isDateTimeType() && nodes[RIGHT].dataType.isDateTimeType()) { if (nodes[LEFT].dataType.isDateTimeTypeWithZone() ^ nodes[RIGHT].dataType.isDateTimeTypeWithZone()) { if (nodes[LEFT].dataType.isDateTimeTypeWithZone()) { nodes[LEFT] = new ExpressionOp(nodes[LEFT]); } if (nodes[RIGHT].dataType.isDateTimeTypeWithZone()) { nodes[RIGHT] = new ExpressionOp(nodes[RIGHT]); } } } } // datetime subtract - type predetermined if (dataType != null) { if (dataType.isIntervalType()) { if (nodes[LEFT].dataType.isDateTimeType() && nodes[RIGHT].dataType.isDateTimeType()) { if (nodes[LEFT].dataType.typeComparisonGroup != nodes[RIGHT].dataType.typeComparisonGroup) { throw Error.error(ErrorCode.X_42562); } } else { Type type = nodes[LEFT].dataType.getCombinedType(session, nodes[RIGHT].dataType, opType); if (type == null) { throw Error.error(ErrorCode.X_42562); } if (type.isIntervalType()) { if (type.typeCode != dataType.typeCode) { throw Error.error(ErrorCode.X_42562); } } else if (type.isNumberType()) { nodes[LEFT] = new ExpressionOp(nodes[LEFT], dataType); nodes[RIGHT] = new ExpressionOp(nodes[RIGHT], dataType); nodes[LEFT].resolveTypes(session, this); nodes[RIGHT].resolveTypes(session, this); } else { throw Error.error(ErrorCode.X_42562); } } } } else { dataType = nodes[LEFT].dataType.getCombinedType(session, nodes[RIGHT].dataType, opType); if (dataType.isDateTimeType()) { if (nodes[LEFT].dataType.isIntervalType()) { if (opType != OpTypes.ADD) { throw Error.error(ErrorCode.X_42563); } Expression temp = nodes[LEFT]; nodes[LEFT] = nodes[RIGHT]; nodes[RIGHT] = temp; } else if (nodes[RIGHT].dataType.isNumberType()) { if (!session.database.sqlSyntaxOra) { throw Error.error(ErrorCode.X_42562); } } } } if (nodes[LEFT].opType == OpTypes.VALUE && nodes[RIGHT].opType == OpTypes.VALUE) { setAsConstantValue(session, parent); } } void resolveTypesForConcat(Session session, Expression parent) { if (dataType != null) { return; } if (nodes[LEFT].isUnresolvedParam()) { nodes[LEFT].dataType = getParameterType(nodes[RIGHT].dataType); } else if (nodes[RIGHT].isUnresolvedParam()) { nodes[RIGHT].dataType = getParameterType(nodes[LEFT].dataType); } if (nodes[LEFT].dataType == null) { nodes[LEFT].dataType = Type.SQL_VARCHAR_DEFAULT; } if (nodes[RIGHT].dataType == null) { nodes[RIGHT].dataType = Type.SQL_VARCHAR_DEFAULT; } if (nodes[LEFT].dataType.isBinaryType() ^ nodes[RIGHT].dataType.isBinaryType()) { throw Error.error(ErrorCode.X_42563); } if (nodes[LEFT].dataType.isArrayType()) { Expression e = nodes[RIGHT]; if (e.opType == OpTypes.ARRAY_ACCESS) { if (parent == null) { throw Error.error(ErrorCode.X_42563); } nodes[RIGHT] = e.getLeftNode(); e.nodes[LEFT] = this; parent.replaceNode(this, e); } } if (nodes[LEFT].dataType.isArrayType() ^ nodes[RIGHT].dataType.isArrayType()) { throw Error.error(ErrorCode.X_42563); } // conversion of right argument to character for backward compatibility if (nodes[LEFT].dataType.isCharacterType() && !nodes[RIGHT].dataType.isCharacterType()) { if (session.database.sqlEnforceTypes) { throw Error.error(ErrorCode.X_42562); } Type newType = CharacterType.getCharacterType(Types.SQL_VARCHAR, nodes[RIGHT].dataType.displaySize(), nodes[LEFT].dataType.getCollation()); nodes[RIGHT] = ExpressionOp.getCastExpression(session, nodes[RIGHT], newType); } if (nodes[RIGHT].dataType.isCharacterType() && !nodes[LEFT].dataType.isCharacterType()) { if (session.database.sqlEnforceTypes) { throw Error.error(ErrorCode.X_42562); } Type newType = CharacterType.getCharacterType(Types.SQL_VARCHAR, nodes[LEFT].dataType.displaySize(), nodes[RIGHT].dataType.getCollation()); nodes[LEFT] = ExpressionOp.getCastExpression(session, nodes[LEFT], newType); } dataType = nodes[LEFT].dataType.getCombinedType(session, nodes[RIGHT].dataType, OpTypes.CONCAT); if (nodes[LEFT].opType == OpTypes.VALUE && nodes[RIGHT].opType == OpTypes.VALUE) { setAsConstantValue(session, parent); } } private Type getParameterType(Type type) { if (type == null) { return null; } switch (type.typeCode) { case Types.SQL_CHAR : case Types.SQL_VARCHAR : return Type.SQL_VARCHAR_DEFAULT; case Types.SQL_CLOB : return Type.SQL_CLOB; case Types.SQL_BINARY : case Types.SQL_VARBINARY : return Type.SQL_VARBINARY_DEFAULT; case Types.SQL_BLOB : return Type.SQL_BLOB; case Types.SQL_BIT : case Types.SQL_BIT_VARYING : return Type.SQL_BIT_VARYING_MAX_LENGTH; case Types.SQL_ARRAY : return type; default : return null; } } public Object getValue(Session session) { switch (opType) { case OpTypes.VALUE : return valueData; case OpTypes.NEGATE : return dataType.negate(nodes[LEFT].getValue(session, nodes[LEFT].dataType)); } Object a = nodes[LEFT].getValue(session); Object b = nodes[RIGHT].getValue(session); switch (opType) { case OpTypes.ADD : return dataType.add(session, a, b, nodes[RIGHT].dataType); case OpTypes.SUBTRACT : return dataType.subtract(session, a, b, nodes[RIGHT].dataType); case OpTypes.MULTIPLY : return dataType.multiply(a, b); case OpTypes.DIVIDE : return dataType.divide(session, a, b); case OpTypes.CONCAT : if (!session.database.sqlConcatNulls && nodes[LEFT].dataType.isCharacterType()) { if (a == null && b != null) { a = ""; } else if (a != null && b == null) { b = ""; } } return dataType.concat(session, a, b); default : throw Error.runtimeError(ErrorCode.U_S0500, "Expression"); } } }