/* 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.ArrayListIdentity;
import org.hsqldb.lib.ArrayUtil;
import org.hsqldb.lib.HsqlArrayList;
import org.hsqldb.lib.HsqlList;
import org.hsqldb.types.ArrayType;
import org.hsqldb.types.RowType;
import org.hsqldb.types.Type;

Implementation of array aggregate operations
Author:Fred Toussi (fredt@users dot sourceforge.net)
Version:2.5.0
Since:2.0.1
/** * Implementation of array aggregate operations * * @author Fred Toussi (fredt@users dot sourceforge.net) * @version 2.5.0 * @since 2.0.1 */
public class ExpressionArrayAggregate extends Expression { SortAndSlice sort; SortAndSlice distinctSort; String separator = ","; ArrayType arrayDataType; Type exprType; Expression condition = Expression.EXPR_TRUE; ExpressionArrayAggregate(int type, boolean distinct, Expression e, SortAndSlice sort, String separator) { super(type); this.isDistinctAggregate = distinct; this.sort = sort; if (separator != null) { this.separator = separator; } if (type == OpTypes.MEDIAN) { nodes = new Expression[]{ e }; return; } if (sort == null) { nodes = new Expression[]{ e }; } else { HsqlArrayList list = sort.getExpressionList(); nodes = new Expression[list.size() + 1]; list.toArray(nodes); nodes[list.size()] = e; sort.prepareExtraColumn(1); } if (isDistinctAggregate) { distinctSort = new SortAndSlice(); distinctSort.prepareSingleColumn(nodes.length - 1); } } public boolean isSelfAggregate() { return true; } public String getSQL() { StringBuilder sb = new StringBuilder(64); String left = getContextSQL(nodes.length > 0 ? nodes[LEFT] : null); switch (opType) { case OpTypes.ARRAY_AGG : sb.append(' ').append(Tokens.T_ARRAY_AGG).append('('); sb.append(left).append(')'); break; case OpTypes.GROUP_CONCAT : sb.append(' ').append(Tokens.T_GROUP_CONCAT).append('('); sb.append(left).append(')'); break; case OpTypes.MEDIAN : sb.append(' ').append(Tokens.T_MEDIAN).append('('); sb.append(left).append(')'); break; default : throw Error.runtimeError(ErrorCode.U_S0500, "ExpressionAggregate"); } 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.ARRAY_AGG : sb.append(Tokens.T_ARRAY_AGG).append(' '); break; case OpTypes.GROUP_CONCAT : sb.append(Tokens.T_GROUP_CONCAT).append(' '); break; case OpTypes.MEDIAN : sb.append(Tokens.T_MEDIAN).append(' '); break; default : } if (getLeftNode() != null) { sb.append(" arg=["); sb.append(nodes[LEFT].describe(session, blanks + 1)); sb.append(']'); } return sb.toString(); } public HsqlList resolveColumnReferences(Session session, RangeGroup rangeGroup, int rangeCount, RangeGroup[] rangeGroups, HsqlList unresolvedSet, boolean acceptsSequences) { HsqlList conditionSet = condition.resolveColumnReferences(session, rangeGroup, rangeCount, rangeGroups, null, false); if (conditionSet != null) { ExpressionColumn.checkColumnsResolved(conditionSet); } if (unresolvedSet == null) { unresolvedSet = new ArrayListIdentity(); } unresolvedSet.add(this); if (rangeGroup.getRangeVariables().length > 0) { this.rangeGroups = rangeGroups; this.rangeGroup = rangeGroup; } return unresolvedSet; } public void resolveTypes(Session session, Expression parent) { nodeDataTypes = new Type[nodes.length]; for (int i = 0; i < nodes.length; i++) { if (nodes[i] != null) { nodes[i].resolveTypes(session, this); if (nodes[i].isUnresolvedParam()) { throw Error.error(ErrorCode.X_42567); } if (nodes[i].dataType == null) { throw Error.error(ErrorCode.X_42567); } nodeDataTypes[i] = nodes[i].dataType; } } exprType = nodes[nodes.length - 1].dataType; if (exprType.isLobType()) { throw Error.error(ErrorCode.X_42534); } if (exprType.isArrayType()) { throw Error.error(ErrorCode.X_42534); } Type rowDataType = new RowType(nodeDataTypes); switch (opType) { case OpTypes.ARRAY_AGG : arrayDataType = new ArrayType(rowDataType, ArrayType.defaultArrayCardinality); dataType = new ArrayType(exprType, ArrayType.defaultArrayCardinality); break; case OpTypes.GROUP_CONCAT : arrayDataType = new ArrayType(rowDataType, ArrayType.defaultArrayCardinality); dataType = Type.SQL_VARCHAR_DEFAULT; break; case OpTypes.MEDIAN : arrayDataType = new ArrayType(nodeDataTypes[0], ArrayType.defaultArrayCardinality); dataType = ExpressionAggregate.getType(session, OpTypes.MEDIAN, exprType); if (!exprType.isNumberType()) { throw Error.error(ErrorCode.X_42563); } break; } condition.resolveTypes(session, null); } boolean equals(Expression other) { if (other instanceof ExpressionArrayAggregate) { ExpressionArrayAggregate o = (ExpressionArrayAggregate) other; return super.equals(other) && opType == other.opType && exprSubType == other.exprSubType && isDistinctAggregate == o.isDistinctAggregate && separator.equals(o.separator) && condition.equals(o.condition); } return false; } public SetFunction updateAggregatingValue(Session session, SetFunction currValue) { if (!condition.testCondition(session)) { return currValue; } Object value = null; Object[] data; switch (opType) { case OpTypes.ARRAY_AGG : data = new Object[nodes.length]; for (int i = 0; i < nodes.length; i++) { data[i] = nodes[i].getValue(session); } value = data; break; case OpTypes.GROUP_CONCAT : data = new Object[nodes.length]; for (int i = 0; i < nodes.length; i++) { data[i] = nodes[i].getValue(session); } if (data[data.length - 1] == null) { return currValue; } value = data; break; case OpTypes.MEDIAN : value = nodes[0].getValue(session); if (value == null) { return currValue; } break; } if (currValue == null) { currValue = new SetFunctionValueArray(); } currValue.add(value); return currValue; } public Object getAggregatedValue(Session session, SetFunction currValue) { if (currValue == null) { return null; } Object[] array = (Object[]) currValue.getValue(); if (isDistinctAggregate) { arrayDataType.sort(session, array, distinctSort); int size = arrayDataType.deDuplicate(session, array, distinctSort); array = (Object[]) ArrayUtil.resizeArrayIfDifferent(array, size); } if (sort != null) { arrayDataType.sort(session, array, sort); } switch (opType) { case OpTypes.ARRAY_AGG : { Object[] resultArray = new Object[array.length]; for (int i = 0; i < array.length; i++) { Object[] row = (Object[]) array[i]; resultArray[i] = row[row.length - 1]; } return resultArray; } case OpTypes.GROUP_CONCAT : { StringBuilder sb = new StringBuilder(16 * array.length); for (int i = 0; i < array.length; i++) { if (i > 0) { sb.append(separator); } Object[] row = (Object[]) array[i]; Object value = row[row.length - 1]; String string = (String) Type.SQL_VARCHAR.convertToType(session, value, exprType); sb.append(string); } return sb.toString(); } case OpTypes.MEDIAN : { SortAndSlice exprSort = new SortAndSlice(); exprSort.prepareSingleColumn(1); arrayDataType.sort(session, array, exprSort); boolean even = array.length % 2 == 0; if (even) { Object val1 = array[(array.length / 2) - 1]; Object val2 = array[array.length / 2]; Object val3 = dataType.add(session, val1, val2, dataType); return dataType.divide(session, val3, Integer.valueOf(2)); } else { return dataType.convertToType(session, array[array.length / 2], exprType); } } } return null; } public Expression getCondition() { return condition; } public boolean hasCondition() { return condition != null && !condition.isTrue(); } public void setCondition(ExpressionLogical e) { condition = e; } public Expression duplicate() { ExpressionArrayAggregate e = (ExpressionArrayAggregate) super.duplicate(); if (condition != null) { e.condition = condition.duplicate(); } return e; } }