package com.revolsys.record.query; import java.sql.PreparedStatement; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import com.akiban.sql.parser.BetweenOperatorNode; import com.akiban.sql.parser.BinaryArithmeticOperatorNode; import com.akiban.sql.parser.BinaryLogicalOperatorNode; import com.akiban.sql.parser.BinaryOperatorNode; import com.akiban.sql.parser.CastNode; import com.akiban.sql.parser.ColumnReference; import com.akiban.sql.parser.ConstantNode; import com.akiban.sql.parser.CursorNode; import com.akiban.sql.parser.InListOperatorNode; import com.akiban.sql.parser.IsNullNode; import com.akiban.sql.parser.JavaToSQLValueNode; import com.akiban.sql.parser.JavaValueNode; import com.akiban.sql.parser.LikeEscapeOperatorNode; import com.akiban.sql.parser.NodeTypes; import com.akiban.sql.parser.NotNode; import com.akiban.sql.parser.NumericConstantNode; import com.akiban.sql.parser.ResultSetNode; import com.akiban.sql.parser.RowConstructorNode; import com.akiban.sql.parser.SQLParser; import com.akiban.sql.parser.SQLToJavaValueNode; import com.akiban.sql.parser.SelectNode; import com.akiban.sql.parser.SimpleStringOperatorNode; import com.akiban.sql.parser.StatementNode; import com.akiban.sql.parser.StaticMethodCallNode; import com.akiban.sql.parser.UserTypeConstantNode; import com.akiban.sql.parser.ValueNode; import com.akiban.sql.parser.ValueNodeList; import com.revolsys.datatype.DataTypes; import com.revolsys.geometry.model.BoundingBox; import com.revolsys.geometry.model.Geometry; import com.revolsys.record.Record; import com.revolsys.record.code.CodeTable; import com.revolsys.record.query.functions.EnvelopeIntersects; import com.revolsys.record.query.functions.Function; import com.revolsys.record.query.functions.GetMapValue; import com.revolsys.record.query.functions.WithinDistance; import com.revolsys.record.schema.FieldDefinition; import com.revolsys.record.schema.RecordDefinition; import com.revolsys.record.schema.RecordStore; import com.revolsys.util.Property; public interface QueryValue extends Cloneable { /** Must be in upper case */ static final List<String> SUPPORTED_BINARY_OPERATORS = Arrays.asList("AND", "OR", "+", "-", "/", "*", "=", "<>", "<", "<=", ">", ">=", "LIKE", "+", "-", "/", "*", "%", "MOD"); static <V extends QueryValue> List<V> cloneQueryValues(final List<V> values) { final List<V> clonedValues = new ArrayList<>(); for (final QueryValue value : values) { if (value == null) { clonedValues.add(null); } else { @SuppressWarnings("unchecked") final V clonedValue = (V)value.clone(); clonedValues.add(clonedValue); } } return clonedValues; } static BoundingBox expand(final BoundingBox boundingBox, final BoundingBox newBoundingBox) { if (boundingBox == null) { return newBoundingBox; } else if (newBoundingBox == null) { return boundingBox; } else { return boundingBox.expandToInclude(newBoundingBox); } } static BoundingBox getBoundingBox(final Query query) { final Condition whereCondition = query.getWhereCondition(); return getBoundingBox(whereCondition); } static BoundingBox getBoundingBox(final QueryValue queryValue) { BoundingBox boundingBox = null; if (queryValue != null) { for (final QueryValue childValue : queryValue.getQueryValues()) { if (childValue instanceof EnvelopeIntersects) { final EnvelopeIntersects intersects = (EnvelopeIntersects)childValue; boundingBox = expand(boundingBox, getBoundingBox(intersects.getBoundingBox1Value())); boundingBox = expand(boundingBox, getBoundingBox(intersects.getBoundingBox2Value())); } else if (childValue instanceof WithinDistance) { final WithinDistance withinDistance = (WithinDistance)childValue; BoundingBox withinBoundingBox = getBoundingBox(withinDistance.getGeometry1Value()); withinBoundingBox = expand(withinBoundingBox, getBoundingBox(withinDistance.getGeometry2Value())); final double distance = ((Number)((Value)withinDistance.getDistanceValue()).getValue()) .doubleValue(); boundingBox = expand(boundingBox, withinBoundingBox.expand(distance)); } else if (childValue instanceof Value) { final Value valueContainer = (Value)childValue; final Object value = valueContainer.getValue(); if (value instanceof BoundingBox) { boundingBox = expand(boundingBox, (BoundingBox)value); } else if (value instanceof Geometry) { final Geometry geometry = (Geometry)value; boundingBox = expand(boundingBox, geometry.getBoundingBox()); } } } } return boundingBox; } static Condition parseWhere(final RecordDefinition recordDefinition, final String whereClause) { if (Property.hasValue(whereClause)) { try { final SQLParser sqlParser = new SQLParser(); String name; if (recordDefinition == null) { name = "Unknown"; } else { name = recordDefinition.getName(); } final String query = "SELECT * FROM \"" + name + "\" WHERE " + whereClause; final StatementNode statement = sqlParser.parseStatement(query); if (statement instanceof CursorNode) { final CursorNode selectStatement = (CursorNode)statement; final ResultSetNode resultSetNode = selectStatement.getResultSetNode(); if (resultSetNode instanceof SelectNode) { final SelectNode selectNode = (SelectNode)resultSetNode; final ValueNode where = selectNode.getWhereClause(); final Condition condition = toQueryValue(recordDefinition, where); return condition; } } return null; } catch (final Throwable e) { throw new IllegalArgumentException("Invalid where clause: " + whereClause, e); } } else { return null; } } @SuppressWarnings("unchecked") static <V extends QueryValue> V toQueryValue(final RecordDefinition recordDefinition, final ValueNode expression) { if (expression instanceof BetweenOperatorNode) { final BetweenOperatorNode betweenExpression = (BetweenOperatorNode)expression; final ValueNode leftValueNode = betweenExpression.getLeftOperand(); final ValueNodeList rightOperandList = betweenExpression.getRightOperandList(); final ValueNode betweenExpressionStart = rightOperandList.get(0); final ValueNode betweenExpressionEnd = rightOperandList.get(1); if (!(leftValueNode instanceof ColumnReference)) { throw new IllegalArgumentException( "Between operator must use a column name not: " + leftValueNode); } if (!(betweenExpressionStart instanceof NumericConstantNode)) { throw new IllegalArgumentException( "Between min value must be a number not: " + betweenExpressionStart); } if (!(betweenExpressionEnd instanceof NumericConstantNode)) { throw new IllegalArgumentException( "Between max value must be a number not: " + betweenExpressionEnd); } final Column column = toQueryValue(recordDefinition, leftValueNode); final Value min = toQueryValue(recordDefinition, betweenExpressionStart); final Value max = toQueryValue(recordDefinition, betweenExpressionEnd); if (recordDefinition != null) { final FieldDefinition field = recordDefinition.getField(column.getName()); min.convert(field); max.convert(field); } return (V)new Between(column, min, max); } else if (expression instanceof BinaryLogicalOperatorNode) { final BinaryLogicalOperatorNode binaryOperatorNode = (BinaryLogicalOperatorNode)expression; final String operator = binaryOperatorNode.getOperator().toUpperCase(); final ValueNode leftValueNode = binaryOperatorNode.getLeftOperand(); final ValueNode rightValueNode = binaryOperatorNode.getRightOperand(); final Condition leftCondition = toQueryValue(recordDefinition, leftValueNode); final Condition rightCondition = toQueryValue(recordDefinition, rightValueNode); if ("AND".equals(operator)) { return (V)new And(leftCondition, rightCondition); } else if ("OR".equals(operator)) { return (V)new Or(leftCondition, rightCondition); } else { throw new IllegalArgumentException( "Binary logical operator " + operator + " not supported."); } } else if (expression instanceof BinaryOperatorNode) { final BinaryOperatorNode binaryOperatorNode = (BinaryOperatorNode)expression; final String operator = binaryOperatorNode.getOperator(); final ValueNode leftValueNode = binaryOperatorNode.getLeftOperand(); final ValueNode rightValueNode = binaryOperatorNode.getRightOperand(); if (SUPPORTED_BINARY_OPERATORS.contains(operator.toUpperCase())) { final QueryValue leftCondition = toQueryValue(recordDefinition, leftValueNode); QueryValue rightCondition = toQueryValue(recordDefinition, rightValueNode); if (leftCondition instanceof Column) { if (rightCondition instanceof Value) { final Column column = (Column)leftCondition; final String name = column.getName(); final Object value = ((Value)rightCondition).getValue(); if (value == null) { throw new IllegalArgumentException( "Values can't be null for " + operator + " use IS NULL or IS NOT NULL instead."); } else if (recordDefinition != null) { final FieldDefinition fieldDefinition = recordDefinition.getField(name); final CodeTable codeTable = recordDefinition.getCodeTableByFieldName(name); if (codeTable == null || fieldDefinition == recordDefinition.getIdField()) { final Object convertedValue = fieldDefinition.toFieldValueException(value); if (convertedValue == null) { throw new IllegalArgumentException("Values can't be null for " + operator + " use IS NULL or IS NOT NULL instead."); } else { rightCondition = new Value(fieldDefinition, convertedValue); } } else { Object id; if (value instanceof String) { final String string = (String)value; final String[] values = string.split(":"); id = codeTable.getIdentifier((Object[])values); } else { id = codeTable.getIdentifier(value); } if (id == null) { throw new IllegalArgumentException(name + "='" + value + "' could not be found in the code table " + codeTable.getName()); } else { rightCondition = new Value(fieldDefinition, id); } } } } } if (expression instanceof BinaryArithmeticOperatorNode) { final QueryValue arithmaticCondition = Q.arithmatic(leftCondition, operator, rightCondition); return (V)arithmaticCondition; } else { final Condition binaryCondition = Q.binary(leftCondition, operator, rightCondition); return (V)binaryCondition; } } else { throw new IllegalArgumentException("Unsupported binary operator " + operator); } } else if (expression instanceof ColumnReference) { final ColumnReference column = (ColumnReference)expression; String columnName = column.getColumnName(); columnName = columnName.replaceAll("\"", ""); if (recordDefinition == null) { return (V)new Column(columnName); } else { final FieldDefinition fieldDefinition = recordDefinition.getField(columnName); if (fieldDefinition == null) { recordDefinition.getField(columnName); throw new IllegalArgumentException("Invalid column name " + columnName); } else { return (V)new Column(fieldDefinition); } } } else if (expression instanceof LikeEscapeOperatorNode) { final LikeEscapeOperatorNode likeEscapeOperatorNode = (LikeEscapeOperatorNode)expression; final ValueNode leftValueNode = likeEscapeOperatorNode.getReceiver(); final ValueNode rightValueNode = likeEscapeOperatorNode.getLeftOperand(); final QueryValue leftCondition = toQueryValue(recordDefinition, leftValueNode); final QueryValue rightCondition = toQueryValue(recordDefinition, rightValueNode); return (V)new ILike(leftCondition, rightCondition); } else if (expression instanceof NotNode) { final NotNode notNode = (NotNode)expression; final ValueNode operand = notNode.getOperand(); final Condition condition = toQueryValue(recordDefinition, operand); return (V)new Not(condition); } else if (expression instanceof InListOperatorNode) { final InListOperatorNode inListOperatorNode = (InListOperatorNode)expression; final ValueNode leftOperand = inListOperatorNode.getLeftOperand(); final QueryValue leftCondition = toQueryValue(recordDefinition, leftOperand); final List<QueryValue> conditions = new ArrayList<>(); final RowConstructorNode itemsList = inListOperatorNode.getRightOperandList(); for (final ValueNode itemValueNode : itemsList.getNodeList()) { final QueryValue itemCondition = toQueryValue(recordDefinition, itemValueNode); conditions.add(itemCondition); } return (V)new In(leftCondition, new CollectionValue(conditions)); } else if (expression instanceof IsNullNode) { final IsNullNode isNullNode = (IsNullNode)expression; final ValueNode operand = isNullNode.getOperand(); final QueryValue value = toQueryValue(recordDefinition, operand); if (isNullNode.getNodeType() == NodeTypes.IS_NOT_NULL_NODE) { return (V)new IsNotNull(value); } else { return (V)new IsNull(value); } // } else if (expression instanceof Parenthesis) { // final Parenthesis parenthesis = (Parenthesis)expression; // final ValueNode parenthesisValueNode = parenthesis.getExpression(); // final Condition condition = toCondition(parenthesisExpression); // final ParenthesisCondition parenthesisCondition = new // ParenthesisCondition( // condition); // if (parenthesis.isNot()) { // return (V)Q.not(parenthesisCondition); // } else { // return (V)parenthesisCondition; // } } else if (expression instanceof RowConstructorNode) { final RowConstructorNode rowConstructorNode = (RowConstructorNode)expression; final ValueNodeList values = rowConstructorNode.getNodeList(); final ValueNode valueNode = values.get(0); return (V)toQueryValue(recordDefinition, valueNode); } else if (expression instanceof UserTypeConstantNode) { final UserTypeConstantNode constant = (UserTypeConstantNode)expression; final Object objectValue = constant.getObjectValue(); return (V)new Value(objectValue); } else if (expression instanceof ConstantNode) { final ConstantNode constant = (ConstantNode)expression; final Object value = constant.getValue(); return (V)new Value(value); } else if (expression instanceof SimpleStringOperatorNode) { final SimpleStringOperatorNode operatorNode = (SimpleStringOperatorNode)expression; final String functionName = operatorNode.getMethodName().toUpperCase(); final ValueNode operand = operatorNode.getOperand(); final QueryValue condition = toQueryValue(recordDefinition, operand); return (V)new Function(functionName, condition); } else if (expression instanceof CastNode) { final CastNode castNode = (CastNode)expression; final String typeName = castNode.getType().getSQLstring(); final ValueNode operand = castNode.getCastOperand(); final QueryValue condition = toQueryValue(recordDefinition, operand); return (V)new Cast(condition, typeName); } else if (expression instanceof JavaToSQLValueNode) { final JavaToSQLValueNode node = (JavaToSQLValueNode)expression; final JavaValueNode javaValueNode = node.getJavaValueNode(); if (javaValueNode instanceof StaticMethodCallNode) { final StaticMethodCallNode methodNode = (StaticMethodCallNode)javaValueNode; final List<QueryValue> parameters = new ArrayList<>(); final String methodName = methodNode.getMethodName(); for (final JavaValueNode parameter : methodNode.getMethodParameters()) { if (parameter instanceof SQLToJavaValueNode) { final SQLToJavaValueNode sqlNode = (SQLToJavaValueNode)parameter; final QueryValue param = toQueryValue(recordDefinition, sqlNode.getSQLValueNode()); parameters.add(param); } } if (methodName.equals("get_map_value")) { return (V)new GetMapValue(parameters); } } return null; } else if (expression == null) { return null; } else { throw new IllegalArgumentException( "Unsupported expression" + expression.getClass() + " " + expression); } } void appendDefaultSql(Query query, RecordStore recordStore, StringBuilder sql); // TODO wrap in a more generic structure int appendParameters(int index, PreparedStatement statement); default void appendSql(final Query query, final RecordStore recordStore, final StringBuilder sql) { if (recordStore == null) { appendDefaultSql(query, null, sql); } else { recordStore.appendQueryValue(query, sql, this); } } QueryValue clone(); default List<QueryValue> getQueryValues() { return Collections.emptyList(); } default String getStringValue(final Record record) { final Object value = getValue(record); return DataTypes.toString(value); } <V> V getValue(Record record); default void setFieldDefinition(final FieldDefinition fieldDefinition) { } default void setRecordDefinition(final RecordDefinition recordDefinition) { for (final QueryValue queryValue : getQueryValues()) { if (queryValue != null) { queryValue.setRecordDefinition(recordDefinition); } } } default String toFormattedString() { return toString(); } @SuppressWarnings("unchecked") default <QV extends QueryValue> QV updateQueryValues( final java.util.function.Function<QueryValue, QueryValue> valueHandler) { return (QV)this; } }