@Override
  public Object[] getRowFromCache( RowMetaInterface lookupMeta, Object[] lookupRow ) throws KettleException {
    if ( stepData.hasDBCondition ) {
      // actually, there was no sense in executing SELECT from db in this case,
      // should be reported as improvement
      return null;
    }

    SearchingContext context = new SearchingContext();
    context.init( keys.length );

    for ( Index index : indexes ) {
      int column = index.getColumn();
      // IS (NOT) NULL operation does not require second argument
      // hence, lookupValue can be absent
      // basically, the index ignores both meta and value, so we can pass everything there
      Object lookupValue = ( column < lookupRow.length ) ? lookupRow[ column ] : null;
      index.applyRestrictionsTo( context, lookupMeta.getValueMeta( column ), lookupValue );
      if ( context.isEmpty() ) {
        // if nothing matches, break the search
        return null;
      }
    }

    // iterate through all elements survived after filtering stage
    // and find the first matching
    BitSet candidates = context.getCandidates();
    int candidate = candidates.nextSetBit( 0 );
    while ( candidate != -1 ) {
      Object[] dataKeys = keys[ candidate ];

      boolean matches = true;
      int lookupShift = 0;
      for ( int i = 0, len = otherConditions.length; i < len && matches; i++ ) {
        int[] columnConditionPair = otherConditions[ i ];

        final int column = columnConditionPair[ 0 ];
        Object keyData = dataKeys[ column ];
        ValueMetaInterface keyMeta = keysMeta.getValueMeta( column );

        int lookupIndex = column + lookupShift;
        Object cmpData = lookupRow[ lookupIndex ];
        ValueMetaInterface cmpMeta = lookupMeta.getValueMeta( lookupIndex );

        int condition = columnConditionPair[ 1 ];
        if ( condition == DatabaseLookupMeta.CONDITION_BETWEEN ) {
          // BETWEEN is a special condition demanding two arguments
          // technically there are no obstacles to implement it,
          // as it is just a short form of: (a <= b) && (b <= c)
          // however, let it be so for now
          matches = ( keyMeta.compare( keyData, cmpMeta, cmpData ) >= 0 );
          if ( matches ) {
            lookupShift++;
            lookupIndex++;
            ValueMetaInterface cmpMeta2 = lookupMeta.getValueMeta( lookupIndex );
            Object cmpData2 = lookupRow[ lookupIndex ];
            matches = ( keyMeta.compare( keyData, cmpMeta2, cmpData2 ) <= 0 );
          }
        } else {
          // if not BETWEEN, than it is LIKE (or some new operator)
          // for now, LIKE is not supported here
          matches = false;
          stepData.hasDBCondition = true;
        }
      }
      if ( matches ) {
        return data[ candidate ];
      } else {
        candidate = candidates.nextSetBit( candidate + 1 );
      }
    }
    return null;
  }
@Test
  public void lookup_DoesNotFind_WithBetweenOperator() throws Exception {
    RowMeta meta = keysMeta.clone();
    meta.setValueMeta( 3, new ValueMetaDate() );
    meta.addValueMeta( new ValueMetaInteger() );

    ReadAllCache cache = buildCache( "<>,IS NOT NULL,BETWEEN,IS NULL" );
    Object[] found = cache.getRowFromCache(
      meta, new Object[] { -1L, null, new Date( 1000 ), new Date( 2000 ), null } );
    assertNull( "(1000 <= keys[2] <= 2000) --> none", found );
  }