gt:         API.Ordering ordering = API.ordering();
context: /**
   * Copyright (C) 2009-2013 FoundationDB, LLC
   *
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU Affero 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 Affero General Public License for more details.
   *
   * You should have received a copy of the GNU Affero General Public License
   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   */
  
  package com.foundationdb.server.test.it.qp;
  
  import com.foundationdb.qp.expression.IndexBound;
  import com.foundationdb.qp.operator.Operator;
  import com.foundationdb.server.types.value.ValueSources;
  
  import org.junit.Ignore;
  import org.junit.Test;
  
  import com.foundationdb.qp.operator.API;
  import com.foundationdb.qp.expression.IndexKeyRange;
  import com.foundationdb.qp.operator.Cursor;
  import com.foundationdb.qp.row.Row;
  import com.foundationdb.qp.rowtype.IndexRowType;
  import com.foundationdb.qp.rowtype.RowType;
  import com.foundationdb.server.api.dml.SetColumnSelector;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import com.foundationdb.qp.rowtype.Schema;
  
  import static com.foundationdb.qp.operator.API.cursor;
  import static com.foundationdb.qp.operator.API.indexScan_Default;
  import static com.foundationdb.server.test.ExpressionGenerators.field;
  import static org.junit.Assert.*;
  
  public class UniqueIndexScanJumpBoundedWithNullsIT extends OperatorITBase
  {
       // Positions of fields within the index row
      private static final int A = 0;
      private static final int B = 1;
      private static final int C = 2;
  
      private static final int COLUMN_COUNT = 3;
  
      private static final boolean ASC = true;
      private static final boolean DESC = false;
  
      private static final SetColumnSelector INDEX_ROW_SELECTOR = new SetColumnSelector(0, 1, 2);
  
      private int t;
      private RowType tRowType;
      private IndexRowType idxRowType;
      private Map<Long, TestRow> indexRowMap = new HashMap<>();
  
      @Override
      protected void setupCreateSchema()
      {
          t = createTable(
              "schema", "t",
              "id int not null primary key",
              "a int",
              "b int",
              "c int");
          createUniqueIndex("schema", "t", "idx", "a", "b", "c");
      }
  
      @Override
      protected void setupPostCreateSchema()
      {
          tRowType = schema.tableRowType(table(t));
          idxRowType = indexType(t, "a", "b", "c");
          db = new Row[] {
              row(t, 1010L, 1L, 11L, 110L),
              row(t, 1011L, 1L, 11L, 111L),
              row(t, 1012L, 1L, (Long)null, 122L),
              row(t, 1013L, 1L, (Long)null, 122L),
              row(t, 1014L, 1L, 13L, 132L),
              row(t, 1015L, 1L, 13L, 133L),
              row(t, 1016L, 1L, null, 122L),
              row(t, 1017L, 1L, 14L, 142L),
              row(t, 1018L, 1L, 30L, 201L),
              row(t, 1019L, 1L, 30L, null),
              row(t, 1020L, 1L, 30L, null),
              row(t, 1021L, 1L, 30L, null),
              row(t, 1022L, 1L, 30L, 300L),
              row(t, 1023L, 1L, 40L, 401L)
          };
          queryContext = queryContext(adapter);
          queryBindings = queryContext.createBindings();
          use(db);
          for (Row row : db) {
              indexRowMap.put(ValueSources.getLong(row.value(0)),
                              new TestRow(tRowType,
                                          ValueSources.toObject(row.value(1)),     // a
                                          ValueSources.toObject(row.value(2)),     // b
                                          ValueSources.toObject(row.value(3)),     // c
                                          ValueSources.toObject(row.value(0))    //id
                                          ));
          }
      }
      
      /**
       * 
       * @param id
       * @return the b column of this id (used to make the lower and upper bound.
       *         This is to avoid confusion as to what 'b' values correspond to what id
       */
      private int b_of(long id)
      {
          return (int)indexRow(id).value(1).getInt32();
      }
  
      @Test
      public void testAAA()
      {
          testSkipNulls(1010,
                        b_of(1010), true,
                        b_of(1015), true,
                        getAAA(),
                        new long[]{1010, 1011, 1014, 1015}); // skip 1012 and 1013
      }
  
      @Test
      public void testAAAToMinNull()
      {
          testSkipNulls(1012, // jump to one of the nulls
                        b_of(1010), true,
                        b_of(1015), true,
                        getAAA(),
                        new long[] {1012, 1013, 1016, 1010, 1011, 1014, 1015}); // should see everything
      }                                                                         // with nulls appearing first
  
      @Test
      public void testDDD()
      {
          testSkipNulls(1015,
                        b_of(1010), true,
                        b_of(1015), true,
                        getDDD(),
                        new long[] {1015, 1014, 1011, 1010}); // skip 1012 and 1013
          
      }
  
      
      @Test
      public void testDDDToFirstNull()
      {
          testSkipNulls(1019, // jump to the first null
                        b_of(1018), true,
                        b_of(1021), true,
                        getDDD(),
                        new long[] {1021, 1020, 1019});   // 3 rows of [1L, 30L, null]
      }                                                   // (The use of (1021, 1020, 1019) is just for demonstrative purpose.
                                                          //  They could be anything as long as their mapping
      @Test                                               // index row is [1L, 30L, null] )
      public void testDDDToMiddleNull()
      {
          testSkipNulls(1020, // jump to the middle null
                        b_of(1018), true,
                        b_of(1021), true,
                        getDDD(),
                        new long[] {1021, 1020, 1019});
      }
  
      @Test
      public void testDDDToLastNull()
      {
          testSkipNulls(1021, // jump to the first null
                        b_of(1018), true,
                        b_of(1021), true,
                        getDDD(),
                        new long[] {1021, 1020, 1019});
      }
  
      @Test
      public void testAAAToFirstNull()
      {
          testSkipNulls(1019, // jump to the first null
                        b_of(1018), true,
                        b_of(1021), true,
                        getAAA(),
                        new long[] {1019, 1020, 1021, 1018, 1022});
      }
   
      @Test
      public void testAAAToMiddleNull()
      {
          testSkipNulls(1020, // jump to the middle null
                        b_of(1018), true,
                        b_of(1021), true,
                        getAAA(),
                        new long[] {1019, 1020, 1021, 1018, 1022});
      }
  
      @Test
      public void testAAAToLastNull()
      {
          testSkipNulls(1021, // jump to the first null
                        b_of(1018), true,
                        b_of(1021), true,
                        getAAA(),
                        new long[] {1019, 1020, 1021, 1018, 1022});
      }
  
      @Test
      public void testDDDToMaxNull()
      {
          testSkipNulls(1016,
                        b_of(1015), false,
                        b_of(1017), true,
                        getDDD(),
                        new long[] {}); 
      }
      
      //TODO: add more test****()
  
      private void testSkipNulls(long targetId,                  // location to jump to
                                 int bLo, boolean lowInclusive,  // lower bound
                                 int bHi, boolean hiInclusive,   // upper bound
                                 API.Ordering ordering,          
                                 long expected[])
      {
          Operator plan = indexScan_Default(idxRowType, bounded(1, bLo, lowInclusive, bHi, hiInclusive), ordering);
          Cursor cursor = cursor(plan, queryContext, queryBindings);
          cursor.openTopLevel();
          cursor.jump(indexRow(targetId), INDEX_ROW_SELECTOR);
  
          Row row;
          List<Row> actualRows = new ArrayList<>();
          
          while ((row = cursor.next()) != null)
          {
              actualRows.add(row);
          }
          cursor.closeTopLevel();
  
               // check the list of rows
          checkRows(actualRows, expected);
      }
  
      private void checkRows(List<Row> actual, long expected[])
      {
          List<Long> actualList = toListOfLong(actual);
          List<Long> expectedList = new ArrayList<>(expected.length);
          for (long val : expected)
              expectedList.add(val);
  
          assertEquals(expectedList, actualList);
      }
  
      private List<Long> toListOfLong(List<Row> rows)
      {
          List<Long> ret = new ArrayList<>(rows.size());
  
          for (Row row : rows)
              ret.add(getLong(row, 3));
  
          return ret;
      }
      
      private API.Ordering getAAA()
      {
          return ordering(A, ASC, B, ASC, C, ASC);
      }
  
      private API.Ordering getAAD()
      {
          return ordering(A, ASC, B, ASC, C, DESC);
      }
      
      private API.Ordering getADA()
      {
          return ordering(A, ASC, B, DESC, C, ASC);
      }
  
      private API.Ordering getDAA()
      {
          return ordering(A, DESC, B, ASC, C, ASC);
      }
  
      private API.Ordering getDAD()
      {
          return ordering(A, DESC, B, ASC, C, DESC);
      }
  
      private API.Ordering getDDA()
      {
          return ordering(A, DESC, B, DESC, C, ASC);
      }
  
  
      private API.Ordering getADD()
      {
           return ordering(A, ASC, B, ASC, C, DESC);
      }
  
      private API.Ordering getDDD()
      {
          return ordering(A, DESC, B, DESC, C, DESC);
      }
  
      private TestRow indexRow(long id)
      {
          return indexRowMap.get(id);
      }
  
      private long[] longs(long... longs)
      {
          return longs;
      }
  
      private IndexKeyRange bounded(long a, long bLo, boolean loInclusive, long bHi, boolean hiInclusive)
      {
          IndexBound lo = new IndexBound(new TestRow(tRowType, new Object[] {a, bLo, null, null}), new SetColumnSelector(0, 1));
          IndexBound hi = new IndexBound(new TestRow(tRowType, new Object[] {a, bHi, null, null}), new SetColumnSelector(0, 1));
          return IndexKeyRange.bounded(idxRowType, lo, loInclusive, hi, hiInclusive);
      }
  
      private API.Ordering ordering(Object... ord) // alternating column positions and asc/desc
      {
  