All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.redhat.lightblue.eval.FieldComparisonEvaluator Maven / Gradle / Ivy

There is a newer version: 2.18.0
Show newest version
/*
 Copyright 2013 Red Hat, Inc. and/or its affiliates.

 This file is part of lightblue.

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see .
 */
package com.redhat.lightblue.eval;

import java.util.List;
import java.util.Iterator;
import java.util.ArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.redhat.lightblue.crud.CrudConstants;
import com.redhat.lightblue.metadata.FieldTreeNode;
import com.redhat.lightblue.metadata.SimpleField;
import com.redhat.lightblue.metadata.ArrayField;
import com.redhat.lightblue.metadata.SimpleArrayElement;
import com.redhat.lightblue.metadata.types.ArrayType;
import com.redhat.lightblue.metadata.Type;
import com.redhat.lightblue.query.BinaryComparisonOperator;
import com.redhat.lightblue.query.FieldComparisonExpression;
import com.redhat.lightblue.util.Path;
import com.redhat.lightblue.util.KeyValueCursor;

/**
 * Evaluates lfield op rfield type comparisons. Both fields can be simple fields
 * or arrays. Here's the semantics of the operation:
 *
 * 
    *
  • lfield op rfield : true if lfield op rfield
  • *
  • lfield op rarray: true if lfield op rarray[i] for all elements i
  • *
  • larray op rarray: true if larray[i] op rarray[i] for all elements i. for * op other than $ne, array sizes must be equal
  • *
* * */ public class FieldComparisonEvaluator extends QueryEvaluator { private static final Logger LOGGER = LoggerFactory.getLogger(FieldComparisonEvaluator.class); private final FieldTreeNode fieldMd; private final FieldTreeNode rfieldMd; private final Path relativePath; private final Path rfieldRelativePath; private final BinaryComparisonOperator operator; /** * Constructs evaluator for {field op field} style comparison * * @param expr The expression * @param md Entity metadata * @param context The path relative to which the expression will be * evaluated */ public FieldComparisonEvaluator(FieldComparisonExpression expr, FieldTreeNode context) { this.relativePath = expr.getField(); this.rfieldRelativePath = expr.getRfield(); fieldMd = context.resolve(relativePath); if (fieldMd == null) { throw new EvaluationError(expr, CrudConstants.ERR_FIELD_NOT_THERE + " " + relativePath); } rfieldMd = context.resolve(rfieldRelativePath); if (rfieldMd == null) { throw new EvaluationError(expr, CrudConstants.ERR_FIELD_NOT_THERE + " " + rfieldRelativePath); } // Both fields must be simple fields or simple arrays if (!(fieldMd instanceof SimpleField || (fieldMd instanceof ArrayField && ((ArrayField) fieldMd).getElement() instanceof SimpleArrayElement))) { throw new EvaluationError(expr, CrudConstants.ERR_EXPECTED_SIMPLE_FIELD_OR_SIMPLE_ARRAY + " " + relativePath); } if (!(rfieldMd instanceof SimpleField || (rfieldMd instanceof ArrayField && ((ArrayField) rfieldMd).getElement() instanceof SimpleArrayElement))) { throw new EvaluationError(expr, CrudConstants.ERR_EXPECTED_SIMPLE_FIELD_OR_SIMPLE_ARRAY + " " + rfieldRelativePath); } operator = expr.getOp(); LOGGER.debug("ctor {} {} {}", relativePath, operator, rfieldRelativePath); } @Override public boolean evaluate(QueryEvaluationContext ctx) { LOGGER.debug("evaluate {} {} {}", relativePath, operator, rfieldRelativePath); KeyValueCursor lcursor = ctx.getNodes(relativePath); ctx.setResult(false); if (lcursor != null) { while (lcursor.hasNext() && !ctx.getResult()) { lcursor.next(); JsonNode lvalueNode = lcursor.getCurrentValue(); Object ldocValue = null; List ldocList = null; if (lvalueNode != null) { if (fieldMd.getType() instanceof ArrayType) { ldocList = makeList(((ArrayField) fieldMd).getElement().getType(), lvalueNode); } else { ldocValue = fieldMd.getType().fromJson(lvalueNode); } } KeyValueCursor rcursor = ctx.getNodes(rfieldRelativePath); if (rcursor != null) { while (rcursor.hasNext() && !ctx.getResult()) { rcursor.next(); JsonNode rvalueNode = rcursor.getCurrentValue(); Object rdocValue = null; List rdocList = null; if (rvalueNode != null) { if (rfieldMd.getType() instanceof ArrayType) { rdocList = makeList(((ArrayField) rfieldMd).getElement().getType(), rvalueNode); } else { rdocValue = rfieldMd.getType().fromJson(rvalueNode); } } LOGGER.debug(" lvalue={} rvalue={}", lvalueNode, rvalueNode); if (ldocValue != null && rdocValue != null) { // Both fields are values int result = fieldMd.getType().compare(ldocValue, rdocValue); LOGGER.debug(" result={}", result); if (operator.apply(result)) { ctx.setResult(true); } } else if (ldocList != null && rdocList != null) { // Both fields are arrays. Compare each element Type type = ((ArrayField) fieldMd).getElement().getType(); int ln = ldocList.size(); int rn = rdocList.size(); int cmp = 0; if (ln == rn) { for (int i = 0; i < ln; i++) { cmp = apply(cmp, type.compare(ldocList.get(i), rdocList.get(i))); } } else { cmp = 0x07; // $ne } LOGGER.debug("Comparing arrays {} {} {}={}", ldocList, operator, rdocList, cmp); if (cmpOp(CMP_LOOKUP[cmp], operator)) { ctx.setResult(true); } } else if (ldocList != null && rdocValue != null) { // Left field is an array, right field is a value BinaryComparisonOperator resultOp = lvCompare(rdocValue, ldocList, ((ArrayField) fieldMd).getElement().getType()).invert(); LOGGER.debug("Comparing array with field {} {} {}={}", ldocList, operator, rdocValue, resultOp); if (cmpOp(resultOp, operator)) { ctx.setResult(true); } } else if (ldocValue != null && rdocList != null) { // left field is a value, right field is an array BinaryComparisonOperator resultOp = lvCompare(ldocValue, rdocList, fieldMd.getType()); LOGGER.debug("Comparing field with array {} {} {}={}", ldocValue, operator, rdocList, resultOp); if (cmpOp(resultOp, operator)) { ctx.setResult(true); } } } } } } return ctx.getResult(); } private static List makeList(Type t, JsonNode node) { if (node instanceof ArrayNode) { List list = new ArrayList<>(node.size()); for (Iterator itr = ((ArrayNode) node).elements(); itr.hasNext();) { list.add(t.fromJson(itr.next())); } return list; } return null; } /** * The result is the result obtained from lvCompare. The * op is the operator for the evaluator. Returns if the result * satisties the operator. */ private static boolean cmpOp(BinaryComparisonOperator result, BinaryComparisonOperator op) { if (result != op) { switch (op) { case _neq: if (result == BinaryComparisonOperator._eq) { return false; } break; case _gte: if (result != BinaryComparisonOperator._gte && result != BinaryComparisonOperator._gt && result != BinaryComparisonOperator._eq) { return false; } break; case _lte: if (result != BinaryComparisonOperator._lte && result != BinaryComparisonOperator._lt && result != BinaryComparisonOperator._eq) { return false; } break; default: return false; } } return true; } /** * Comparison lookup table. A 3-bit value is the index into this table Bit0 * means there are greater values. Bit1 means there are equal values. Bit2 * means there are less values. */ private static final BinaryComparisonOperator CMP_LOOKUP[] = { BinaryComparisonOperator._neq, // 000 BinaryComparisonOperator._gt, // 001 BinaryComparisonOperator._eq, // 010 BinaryComparisonOperator._gte, // 011 BinaryComparisonOperator._lt, // 100 BinaryComparisonOperator._neq, // 101 BinaryComparisonOperator._lte, // 110 BinaryComparisonOperator._neq // 111 }; private static int apply(int cmp, int result) { if (result == 0) { return cmp | 0x02; } else if (result < 0) { return cmp | 0x04; } else { return cmp | 0x01; } } /** * Compare a value to a list. Returns: *
    *
  • _eq: if all values in list are equal to value
  • *
  • _lt: if value is less than all values in the list
  • *
  • _lte: if value is less than or equal to all the values in the * list
  • *
  • _gt: if value is greater than all values in the list
  • *
  • _gte: if value is greater or equal to all the values in the list
  • *
  • _neq: otherwise
  • *
      */ private static BinaryComparisonOperator lvCompare(Object value, List list, Type t) { int cmp = 0; if (value != null) { if (list != null && !list.isEmpty()) { for (Object x : list) { cmp = apply(cmp, t.compare(value, x)); } } } return CMP_LOOKUP[cmp]; } }