/* 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.BinaryData;
import org.hsqldb.types.Type;
import org.hsqldb.types.Types;

Implementation of LIKE operations
Author:Campbell Burnet (campbell-burnet@users dot sourceforge.net), Fred Toussi (fredt@users dot sourceforge.net)
Version:2.3.3
Since:1.9.0
/** * Implementation of LIKE operations * * @author Campbell Burnet (campbell-burnet@users dot sourceforge.net) * @author Fred Toussi (fredt@users dot sourceforge.net) * @version 2.3.3 * @since 1.9.0 */
public final class ExpressionLike extends ExpressionLogical { private static final int ESCAPE = 2; private Like likeObject;
Creates a LIKE expression
/** * Creates a LIKE expression */
ExpressionLike(Expression left, Expression right, Expression escape, boolean noOptimisation) { super(OpTypes.LIKE); nodes = new Expression[TERNARY]; nodes[LEFT] = left; nodes[RIGHT] = right; nodes[ESCAPE] = escape; likeObject = new Like(); this.noOptimisation = noOptimisation; } private ExpressionLike(ExpressionLike other) { super(OpTypes.LIKE); this.nodes = other.nodes; this.likeObject = other.likeObject; } public HsqlList resolveColumnReferences(Session session, RangeGroup rangeGroup, int rangeCount, RangeGroup[] rangeGroups, HsqlList unresolvedSet, boolean acceptsSequences) { for (int i = 0; i < nodes.length; i++) { if (nodes[i] != null) { unresolvedSet = nodes[i].resolveColumnReferences(session, rangeGroup, rangeCount, rangeGroups, unresolvedSet, acceptsSequences); } } return unresolvedSet; } public Object getValue(Session session) { if (opType != OpTypes.LIKE) { return super.getValue(session); } Object leftValue = nodes[LEFT].getValue(session); Object rightValue = nodes[RIGHT].getValue(session); Object escapeValue = nodes[ESCAPE] == null ? null : nodes[ESCAPE].getValue( session); if (likeObject.isVariable) { synchronized (likeObject) { likeObject.setPattern(session, rightValue, escapeValue, nodes[ESCAPE] != null); return likeObject.compare(session, leftValue); } } return likeObject.compare(session, leftValue); } public void resolveTypes(Session session, Expression parent) { if (opType != OpTypes.LIKE) { return; } for (int i = 0; i < nodes.length; i++) { if (nodes[i] != null) { nodes[i].resolveTypes(session, this); } } if (nodes[LEFT].isUnresolvedParam() && nodes[RIGHT].isUnresolvedParam()) { nodes[LEFT].dataType = Type.SQL_VARCHAR_DEFAULT; } if (nodes[LEFT].dataType == null && nodes[RIGHT].dataType == null) { throw Error.error(ErrorCode.X_42567); } if (nodes[LEFT].isUnresolvedParam()) { nodes[LEFT].dataType = nodes[RIGHT].dataType.isBinaryType() ? Type.SQL_VARBINARY_DEFAULT : Type.SQL_VARCHAR_DEFAULT; } else if (nodes[RIGHT].isUnresolvedParam()) { nodes[RIGHT].dataType = nodes[LEFT].dataType.isBinaryType() ? Type.SQL_VARBINARY_DEFAULT : Type.SQL_VARCHAR_DEFAULT; } if (nodes[LEFT].dataType == null || nodes[RIGHT].dataType == null) { throw Error.error(ErrorCode.X_42567); } int group = nodes[LEFT].dataType.typeComparisonGroup; if (group == Types.SQL_VARCHAR) { // } else if (group == Types.SQL_VARBINARY) { likeObject.isBinary = true; } else { if (session.database.sqlEnforceTypes) { throw Error.error(ErrorCode.X_42562); } if (group == Types.OTHER) { throw Error.error(ErrorCode.X_42563); } nodes[LEFT] = ExpressionOp.getCastExpression(session, nodes[LEFT], Type.SQL_VARCHAR_DEFAULT); group = Types.SQL_VARCHAR; } if (nodes[RIGHT].dataType.typeComparisonGroup != group) { throw Error.error(ErrorCode.X_42563); } if (group == Types.SQL_VARCHAR) { boolean ignoreCase = !nodes[LEFT].dataType.getCollation().isCaseSensitive() || !nodes[RIGHT].dataType.getCollation().isCaseSensitive(); likeObject.setIgnoreCase(ignoreCase); } likeObject.dataType = nodes[LEFT].dataType; boolean isEscapeFixedConstant = true; if (nodes[ESCAPE] != null) { if (nodes[ESCAPE].isUnresolvedParam()) { nodes[ESCAPE].dataType = likeObject.isBinary ? Type.SQL_VARBINARY : Type.SQL_VARCHAR; } if (nodes[ESCAPE].dataType.typeComparisonGroup != group) { throw Error.error(ErrorCode.X_42563); } nodes[ESCAPE].resolveTypes(session, this); isEscapeFixedConstant = nodes[ESCAPE].opType == OpTypes.VALUE; if (isEscapeFixedConstant) { nodes[ESCAPE].setAsConstantValue(session, parent); if (nodes[ESCAPE].dataType == null) { throw Error.error(ErrorCode.X_42567); } if (nodes[ESCAPE].valueData != null) { long length; switch (nodes[ESCAPE].dataType.typeCode) { case Types.SQL_CHAR : case Types.SQL_VARCHAR : length = ((String) nodes[ESCAPE].valueData).length(); break; case Types.SQL_BINARY : case Types.SQL_VARBINARY : length = ((BinaryData) nodes[ESCAPE].valueData).length( session); break; default : throw Error.error(ErrorCode.X_42563); } if (length != 1) { throw Error.error(ErrorCode.X_22019); } } } } boolean isRightArgFixedConstant = nodes[RIGHT].opType == OpTypes.VALUE; if (isRightArgFixedConstant && isEscapeFixedConstant) { if (nodes[LEFT].opType == OpTypes.VALUE) { setAsConstantValue(session, parent); likeObject = null; return; } likeObject.isVariable = false; } // always optimise with logical conditions Object pattern = isRightArgFixedConstant ? nodes[RIGHT].getValue(session) : null; boolean constantEscape = isEscapeFixedConstant && nodes[ESCAPE] != null; Object escape = constantEscape ? nodes[ESCAPE].getValue(session) : null; likeObject.setPattern(session, pattern, escape, nodes[ESCAPE] != null); if (noOptimisation) { return; } if (likeObject.isEquivalentToUnknownPredicate()) { setAsConstantValue(session, parent); likeObject = null; return; } if (likeObject.isEquivalentToEqualsPredicate()) { opType = OpTypes.EQUAL; nodes[RIGHT] = new ExpressionValue(likeObject.getRangeLow(), Type.SQL_VARCHAR); likeObject = null; setEqualityMode(); return; } if (likeObject.isEquivalentToNotNullPredicate()) { Expression notNull = new ExpressionLogical(OpTypes.IS_NULL, nodes[LEFT]); opType = OpTypes.NOT; nodes = new Expression[UNARY]; nodes[LEFT] = notNull; likeObject = null; return; } if (nodes[LEFT].opType == OpTypes.COLUMN) { ExpressionLike newLike = new ExpressionLike(this); Expression prefix = new ExpressionOp(OpTypes.LIKE_ARG, nodes[RIGHT], nodes[ESCAPE]); prefix.resolveTypes(session, null); Expression cast = new ExpressionOp(OpTypes.PREFIX, nodes[LEFT], prefix); Expression equ = new ExpressionLogical(OpTypes.EQUAL, cast, prefix); equ = new ExpressionLogical(OpTypes.GREATER_EQUAL_PRE, nodes[LEFT], prefix, equ); nodes = new Expression[BINARY]; likeObject = null; nodes[LEFT] = equ; nodes[RIGHT] = newLike; opType = OpTypes.AND; } } public String getSQL() { if (likeObject == null) { return super.getSQL(); } String left = getContextSQL(nodes[LEFT]); String right = getContextSQL(nodes[RIGHT]); StringBuilder sb = new StringBuilder(); sb.append(left).append(' ').append(Tokens.T_LIKE).append(' '); sb.append(right); /** @todo fredt - scripting of non-ascii escapes needs changes to general script logging */ if (nodes[ESCAPE] != null) { sb.append(' ').append(Tokens.T_ESCAPE).append(' '); sb.append(nodes[ESCAPE].getSQL()); sb.append(' '); } return sb.toString(); } protected String describe(Session session, int blanks) { if (likeObject == null) { return super.describe(session, blanks); } StringBuilder sb = new StringBuilder(); sb.append('\n'); for (int i = 0; i < blanks; i++) { sb.append(' '); } sb.append("LIKE "); sb.append(likeObject.describe(session)); return sb.toString(); } public Expression duplicate() { ExpressionLike e = (ExpressionLike) super.duplicate(); if (likeObject != null) { e.likeObject = likeObject.duplicate(); } return e; } }