private static Collection<?> foreach( KeyRange[][] ranges, int[] widths, Expectation... expectations) { List<List<KeyRange>> cnf = Lists.transform(Lists.newArrayList(ranges), ARRAY_TO_LIST); List<Object> ret = Lists.newArrayList(); ret.add(new Object[] {cnf, widths, Arrays.asList(expectations)}); return ret; }
private static Collection<?> foreach( ScanRanges scanRanges, int[] widths, KeyRange[] expectedSplits) { SkipScanFilter filter = new SkipScanFilter(scanRanges.getRanges(), buildSchema(widths)); Scan scan = new Scan().setFilter(filter).setStartRow(KeyRange.UNBOUND).setStopRow(KeyRange.UNBOUND); List<Object> ret = Lists.newArrayList(); ret.add(new Object[] {scan, scanRanges, Arrays.<KeyRange>asList(expectedSplits)}); return ret; }
private static Collection<?> foreach( KeyRange[][] ranges, int[] widths, KeyRange[] expectedSplits) { RowKeySchema schema = buildSchema(widths); List<List<KeyRange>> slots = Lists.transform(Lists.newArrayList(ranges), ARRAY_TO_LIST); SkipScanFilter filter = new SkipScanFilter(slots, schema); // Always set start and stop key to max to verify we are using the information in skipscan // filter over the scan's KMIN and KMAX. Scan scan = new Scan().setFilter(filter).setStartRow(KeyRange.UNBOUND).setStopRow(KeyRange.UNBOUND); ScanRanges scanRanges = ScanRanges.create(slots, schema); List<Object> ret = Lists.newArrayList(); ret.add(new Object[] {scan, scanRanges, Arrays.<KeyRange>asList(expectedSplits)}); return ret; }
public MutationPlan compile(UpsertStatement upsert, List<Object> binds) throws SQLException { final PhoenixConnection connection = statement.getConnection(); ConnectionQueryServices services = connection.getQueryServices(); final int maxSize = services .getProps() .getInt( QueryServices.MAX_MUTATION_SIZE_ATTRIB, QueryServicesOptions.DEFAULT_MAX_MUTATION_SIZE); final ColumnResolver resolver = FromCompiler.getResolver(upsert, connection); final TableRef tableRef = resolver.getTables().get(0); PTable table = tableRef.getTable(); if (table.getType() == PTableType.VIEW) { throw new ReadOnlyTableException("Mutations not allowed for a view (" + tableRef + ")"); } Scan scan = new Scan(); final StatementContext context = new StatementContext(connection, resolver, binds, upsert.getBindCount(), scan); // Setup array of column indexes parallel to values that are going to be set List<ColumnName> columnNodes = upsert.getColumns(); List<PColumn> allColumns = table.getColumns(); int[] columnIndexesToBe; int[] pkSlotIndexesToBe; PColumn[] targetColumns; // Allow full row upsert if no columns or only dynamic one are specified and values count match if (columnNodes.isEmpty() || columnNodes.size() == upsert.getTable().getDynamicColumns().size()) { columnIndexesToBe = new int[allColumns.size()]; pkSlotIndexesToBe = new int[columnIndexesToBe.length]; targetColumns = new PColumn[columnIndexesToBe.length]; int j = table.getBucketNum() == null ? 0 : 1; // Skip over the salting byte. for (int i = 0; i < allColumns.size(); i++) { columnIndexesToBe[i] = i; targetColumns[i] = allColumns.get(i); if (SchemaUtil.isPKColumn(allColumns.get(i))) { pkSlotIndexesToBe[i] = j++; } } } else { columnIndexesToBe = new int[columnNodes.size()]; pkSlotIndexesToBe = new int[columnIndexesToBe.length]; targetColumns = new PColumn[columnIndexesToBe.length]; Arrays.fill( columnIndexesToBe, -1); // TODO: necessary? So we'll get an AIOB exception if it's not replaced Arrays.fill( pkSlotIndexesToBe, -1); // TODO: necessary? So we'll get an AIOB exception if it's not replaced BitSet pkColumnsSet = new BitSet(table.getPKColumns().size()); for (int i = 0; i < columnNodes.size(); i++) { ColumnName colName = columnNodes.get(i); ColumnRef ref = resolver.resolveColumn(null, colName.getFamilyName(), colName.getColumnName()); columnIndexesToBe[i] = ref.getColumnPosition(); targetColumns[i] = ref.getColumn(); if (SchemaUtil.isPKColumn(ref.getColumn())) { pkColumnsSet.set(pkSlotIndexesToBe[i] = ref.getPKSlotPosition()); } } int i = table.getBucketNum() == null ? 0 : 1; for (; i < table.getPKColumns().size(); i++) { PColumn pkCol = table.getPKColumns().get(i); if (!pkColumnsSet.get(i)) { if (!pkCol.isNullable()) { throw new ConstraintViolationException( table.getName().getString() + "." + pkCol.getName().getString() + " may not be null"); } } } } List<ParseNode> valueNodes = upsert.getValues(); QueryPlan plan = null; RowProjector projector = null; int nValuesToSet; boolean runOnServer = false; if (valueNodes == null) { SelectStatement select = upsert.getSelect(); assert (select != null); TableRef selectTableRef = FromCompiler.getResolver(select, connection).getTables().get(0); boolean sameTable = tableRef.equals(selectTableRef); // Pass scan through if same table in upsert and select so that projection is computed // correctly QueryCompiler compiler = new QueryCompiler(connection, 0, sameTable ? scan : new Scan(), targetColumns); plan = compiler.compile(select, binds); projector = plan.getProjector(); nValuesToSet = projector.getColumnCount(); // Cannot auto commit if doing aggregation or topN or salted // Salted causes problems because the row may end up living on a different region runOnServer = plan.getGroupBy() == null && sameTable && select.getOrderBy().isEmpty() && table.getBucketNum() == null; } else { nValuesToSet = valueNodes.size(); } final QueryPlan queryPlan = plan; // Resize down to allow a subset of columns to be specifiable if (columnNodes.isEmpty()) { columnIndexesToBe = Arrays.copyOf(columnIndexesToBe, nValuesToSet); pkSlotIndexesToBe = Arrays.copyOf(pkSlotIndexesToBe, nValuesToSet); } if (nValuesToSet != columnIndexesToBe.length) { throw new SQLExceptionInfo.Builder(SQLExceptionCode.UPSERT_COLUMN_NUMBERS_MISMATCH) .setMessage( "Numbers of columns: " + columnIndexesToBe.length + ". Number of values: " + nValuesToSet) .build() .buildException(); } final int[] columnIndexes = columnIndexesToBe; final int[] pkSlotIndexes = pkSlotIndexesToBe; // TODO: break this up into multiple functions //////////////////////////////////////////////////////////////////// // UPSERT SELECT ///////////////////////////////////////////////////////////////////// if (valueNodes == null) { /* We can run the upsert in a coprocessor if: * 1) the into table matches from table * 2) the select query isn't doing aggregation * 3) autoCommit is on * 4) not topN * Otherwise, run the query to pull the data from the server * and populate the MutationState (upto a limit). */ final boolean isAutoCommit = connection.getAutoCommit(); runOnServer &= isAutoCommit; //////////////////////////////////////////////////////////////////// // UPSERT SELECT run server-side (maybe) ///////////////////////////////////////////////////////////////////// if (runOnServer) { // At most this array will grow bigger by the number of PK columns int[] allColumnsIndexes = Arrays.copyOf(columnIndexes, columnIndexes.length + nValuesToSet); int[] reverseColumnIndexes = new int[table.getColumns().size()]; List<Expression> projectedExpressions = Lists.newArrayListWithExpectedSize(reverseColumnIndexes.length); Arrays.fill(reverseColumnIndexes, -1); for (int i = 0; i < nValuesToSet; i++) { projectedExpressions.add(projector.getColumnProjector(i).getExpression()); reverseColumnIndexes[columnIndexes[i]] = i; } /* * Order projected columns and projected expressions with PK columns * leading order by slot position */ int offset = table.getBucketNum() == null ? 0 : 1; for (int i = 0; i < table.getPKColumns().size() - offset; i++) { PColumn column = table.getPKColumns().get(i + offset); int pos = reverseColumnIndexes[column.getPosition()]; if (pos == -1) { // Last PK column may be fixed width and nullable // We don't want to insert a null expression b/c // it's not valid to set a fixed width type to null. if (column.getDataType().isFixedWidth()) { continue; } // Add literal null for missing PK columns pos = projectedExpressions.size(); Expression literalNull = LiteralExpression.newConstant(null, column.getDataType()); projectedExpressions.add(literalNull); allColumnsIndexes[pos] = column.getPosition(); } // Swap select expression at pos with i Collections.swap(projectedExpressions, i, pos); // Swap column indexes and reverse column indexes too int tempPos = allColumnsIndexes[i]; allColumnsIndexes[i] = allColumnsIndexes[pos]; allColumnsIndexes[pos] = tempPos; reverseColumnIndexes[tempPos] = reverseColumnIndexes[i]; reverseColumnIndexes[i] = i; } // If any pk slots are changing, be conservative and don't run this server side. // If the row ends up living in a different region, we'll get an error otherwise. for (int i = 0; i < table.getPKColumns().size(); i++) { PColumn column = table.getPKColumns().get(i); Expression source = projectedExpressions.get(i); if (source == null || !source.equals( new ColumnRef(tableRef, column.getPosition()).newColumnExpression())) { // TODO: we could check the region boundaries to see if the pk will still be in it. runOnServer = false; // bail on running server side, since PK may be changing break; } } //////////////////////////////////////////////////////////////////// // UPSERT SELECT run server-side ///////////////////////////////////////////////////////////////////// if (runOnServer) { // Iterate through columns being projected List<PColumn> projectedColumns = Lists.newArrayListWithExpectedSize(projectedExpressions.size()); for (int i = 0; i < projectedExpressions.size(); i++) { // Must make new column if position has changed PColumn column = allColumns.get(allColumnsIndexes[i]); projectedColumns.add(column.getPosition() == i ? column : new PColumnImpl(column, i)); } // Build table from projectedColumns PTable projectedTable = PTableImpl.makePTable( table.getName(), table.getType(), table.getTimeStamp(), table.getSequenceNumber(), table.getPKName(), table.getBucketNum(), projectedColumns); // Remove projection of empty column, since it can lead to problems when building another // projection // using this same scan. TODO: move projection code to a later stage, like // QueryPlan.newScanner to // prevent having to do this. ScanUtil.removeEmptyColumnFamily(context.getScan(), table); List<AliasedNode> select = Collections.<AliasedNode>singletonList( NODE_FACTORY.aliasedNode( null, NODE_FACTORY.function( CountAggregateFunction.NORMALIZED_NAME, LiteralParseNode.STAR))); // Ignore order by - it has no impact final RowProjector aggProjector = ProjectionCompiler.getRowProjector( context, select, false, GroupBy.EMPTY_GROUP_BY, OrderBy.EMPTY_ORDER_BY, null); /* * Transfer over PTable representing subset of columns selected, but all PK columns. * Move columns setting PK first in pkSlot order, adding LiteralExpression of null for any missing ones. * Transfer over List<Expression> for projection. * In region scan, evaluate expressions in order, collecting first n columns for PK and collection non PK in mutation Map * Create the PRow and get the mutations, adding them to the batch */ scan.setAttribute( UngroupedAggregateRegionObserver.UPSERT_SELECT_TABLE, UngroupedAggregateRegionObserver.serialize(projectedTable)); scan.setAttribute( UngroupedAggregateRegionObserver.UPSERT_SELECT_EXPRS, UngroupedAggregateRegionObserver.serialize(projectedExpressions)); final QueryPlan aggPlan = new AggregatePlan( context, tableRef, projector, null, GroupBy.EMPTY_GROUP_BY, false, null, OrderBy.EMPTY_ORDER_BY); return new MutationPlan() { @Override public PhoenixConnection getConnection() { return connection; } @Override public ParameterMetaData getParameterMetaData() { return context.getBindManager().getParameterMetaData(); } @Override public MutationState execute() throws SQLException { Scanner scanner = aggPlan.getScanner(); ResultIterator iterator = scanner.iterator(); try { Tuple row = iterator.next(); ImmutableBytesWritable ptr = context.getTempPtr(); final long mutationCount = (Long) aggProjector.getColumnProjector(0).getValue(row, PDataType.LONG, ptr); return new MutationState(maxSize, connection) { @Override public long getUpdateCount() { return mutationCount; } }; } finally { iterator.close(); } } @Override public ExplainPlan getExplainPlan() throws SQLException { List<String> queryPlanSteps = aggPlan.getExplainPlan().getPlanSteps(); List<String> planSteps = Lists.newArrayListWithExpectedSize(queryPlanSteps.size() + 1); planSteps.add("UPSERT ROWS"); planSteps.addAll(queryPlanSteps); return new ExplainPlan(planSteps); } }; } } //////////////////////////////////////////////////////////////////// // UPSERT SELECT run client-side ///////////////////////////////////////////////////////////////////// final int batchSize = Math.min(connection.getMutateBatchSize(), maxSize); return new MutationPlan() { @Override public PhoenixConnection getConnection() { return connection; } @Override public ParameterMetaData getParameterMetaData() { return context.getBindManager().getParameterMetaData(); } @Override public MutationState execute() throws SQLException { byte[][] values = new byte[columnIndexes.length][]; Scanner scanner = queryPlan.getScanner(); int estSize = scanner.getEstimatedSize(); int rowCount = 0; Map<ImmutableBytesPtr, Map<PColumn, byte[]>> mutation = Maps.newHashMapWithExpectedSize(estSize); ResultSet rs = new PhoenixResultSet(scanner, statement); PTable table = tableRef.getTable(); PColumn column; while (rs.next()) { for (int i = 0; i < values.length; i++) { column = table.getColumns().get(columnIndexes[i]); byte[] byteValue = rs.getBytes(i + 1); Object value = rs.getObject(i + 1); int rsPrecision = rs.getMetaData().getPrecision(i + 1); Integer precision = rsPrecision == 0 ? null : rsPrecision; int rsScale = rs.getMetaData().getScale(i + 1); Integer scale = rsScale == 0 ? null : rsScale; // If ColumnModifier from expression in SELECT doesn't match the // column being projected into then invert the bits. if (column.getColumnModifier() == ColumnModifier.SORT_DESC) { byte[] tempByteValue = Arrays.copyOf(byteValue, byteValue.length); byteValue = ColumnModifier.SORT_DESC.apply(byteValue, tempByteValue, 0, byteValue.length); } // We are guaranteed that the two column will have compatible types, // as we checked that before. if (!column .getDataType() .isSizeCompatible( column.getDataType(), value, byteValue, precision, column.getMaxLength(), scale, column.getScale())) { throw new SQLExceptionInfo.Builder(SQLExceptionCode.DATA_INCOMPATIBLE_WITH_TYPE) .setColumnName(column.getName().getString()) .build() .buildException(); } values[i] = column .getDataType() .coerceBytes( byteValue, value, column.getDataType(), precision, scale, column.getMaxLength(), column.getScale()); } setValues(values, pkSlotIndexes, columnIndexes, table, mutation); rowCount++; // Commit a batch if auto commit is true and we're at our batch size if (isAutoCommit && rowCount % batchSize == 0) { MutationState state = new MutationState(tableRef, mutation, 0, maxSize, connection); connection.getMutationState().join(state); connection.commit(); mutation.clear(); } } // If auto commit is true, this last batch will be committed upon return return new MutationState( tableRef, mutation, rowCount / batchSize * batchSize, maxSize, connection); } @Override public ExplainPlan getExplainPlan() throws SQLException { List<String> queryPlanSteps = queryPlan.getExplainPlan().getPlanSteps(); List<String> planSteps = Lists.newArrayListWithExpectedSize(queryPlanSteps.size() + 1); planSteps.add("UPSERT SELECT"); planSteps.addAll(queryPlanSteps); return new ExplainPlan(planSteps); } }; } //////////////////////////////////////////////////////////////////// // UPSERT VALUES ///////////////////////////////////////////////////////////////////// int nodeIndex = 0; // Allocate array based on size of all columns in table, // since some values may not be set (if they're nullable). UpsertValuesCompiler expressionBuilder = new UpsertValuesCompiler(context); final byte[][] values = new byte[nValuesToSet][]; for (ParseNode valueNode : valueNodes) { if (!valueNode.isConstant()) { throw new SQLExceptionInfo.Builder(SQLExceptionCode.VALUE_IN_UPSERT_NOT_CONSTANT) .build() .buildException(); } PColumn column = allColumns.get(columnIndexes[nodeIndex]); expressionBuilder.setColumn(column); LiteralExpression literalExpression = (LiteralExpression) valueNode.accept(expressionBuilder); byte[] byteValue = literalExpression.getBytes(); if (literalExpression.getDataType() != null) { // If ColumnModifier from expression in SELECT doesn't match the // column being projected into then invert the bits. if (literalExpression.getColumnModifier() != column.getColumnModifier()) { byte[] tempByteValue = Arrays.copyOf(byteValue, byteValue.length); byteValue = ColumnModifier.SORT_DESC.apply(byteValue, tempByteValue, 0, byteValue.length); } if (!literalExpression .getDataType() .isCoercibleTo(column.getDataType(), literalExpression.getValue())) { throw new TypeMismatchException( literalExpression.getDataType(), column.getDataType(), "expression: " + literalExpression.toString() + " in column " + column); } if (!column .getDataType() .isSizeCompatible( literalExpression.getDataType(), literalExpression.getValue(), byteValue, literalExpression.getMaxLength(), column.getMaxLength(), literalExpression.getScale(), column.getScale())) { throw new SQLExceptionInfo.Builder(SQLExceptionCode.DATA_INCOMPATIBLE_WITH_TYPE) .setColumnName(column.getName().getString()) .setMessage("value=" + literalExpression.toString()) .build() .buildException(); } } byteValue = column .getDataType() .coerceBytes( byteValue, literalExpression.getValue(), literalExpression.getDataType(), literalExpression.getMaxLength(), literalExpression.getScale(), column.getMaxLength(), column.getScale()); values[nodeIndex] = byteValue; nodeIndex++; } return new MutationPlan() { @Override public PhoenixConnection getConnection() { return connection; } @Override public ParameterMetaData getParameterMetaData() { return context.getBindManager().getParameterMetaData(); } @Override public MutationState execute() { Map<ImmutableBytesPtr, Map<PColumn, byte[]>> mutation = Maps.newHashMapWithExpectedSize(1); setValues(values, pkSlotIndexes, columnIndexes, tableRef.getTable(), mutation); return new MutationState(tableRef, mutation, 0, maxSize, connection); } @Override public ExplainPlan getExplainPlan() throws SQLException { return new ExplainPlan(Collections.singletonList("PUT SINGLE ROW")); } }; }
@Test public void testGetSplitsWithSkipScanFilter() throws Exception { long ts = nextTimestamp(); TableRef table = initTableValues(ts, 3, 5); NavigableMap<HRegionInfo, ServerName> regions = getRegions(table); List<KeyRange> splits = getSplits(table, scan, regions, scanRanges); assertEquals( "Unexpected number of splits: " + splits.size(), expectedSplits.size(), splits.size()); for (int i = 0; i < expectedSplits.size(); i++) { assertEquals(expectedSplits.get(i), splits.get(i)); } assertEquals( "Unexpected number of splits: " + splits.size(), expectedSplits.size(), splits.size()); for (int i = 0; i < expectedSplits.size(); i++) { assertEquals(expectedSplits.get(i), splits.get(i)); } }
@Parameters(name = "{0} {1} {2}") public static Collection<Object> data() { List<Object> testCases = Lists.newArrayList(); // Scan range is empty. testCases.addAll(foreach(ScanRanges.NOTHING, new int[] {1, 1, 1}, new KeyRange[] {})); // Scan range is everything. testCases.addAll( foreach( ScanRanges.EVERYTHING, new int[] {1, 1, 1}, new KeyRange[] { getKeyRange(KeyRange.UNBOUND, true, Ka1A, false), getKeyRange(Ka1A, true, Ka1B, false), getKeyRange(Ka1B, true, Ka1E, false), getKeyRange(Ka1E, true, Ka1G, false), getKeyRange(Ka1G, true, Ka1I, false), getKeyRange(Ka1I, true, Ka2A, false), getKeyRange(Ka2A, true, KeyRange.UNBOUND, false) })); // Scan range lies inside first region. testCases.addAll( foreach( new KeyRange[][] { {getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("a"), true)}, {getKeyRange(Bytes.toBytes("0"), true, Bytes.toBytes("0"), true)}, {getKeyRange(Bytes.toBytes("A"), true, Bytes.toBytes("Z"), true)} }, new int[] {1, 1, 1}, new KeyRange[] {getKeyRange(KeyRange.UNBOUND, true, Ka1A, false)})); // Scan range lies in between first and second, intersecting bound on second. testCases.addAll( foreach( new KeyRange[][] { {getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("a"), true)}, { getKeyRange(Bytes.toBytes("0"), true, Bytes.toBytes("0"), true), getKeyRange(Bytes.toBytes("1"), true, Bytes.toBytes("1"), true) }, {getKeyRange(Bytes.toBytes("A"), true, Bytes.toBytes("A"), true)} }, new int[] {1, 1, 1}, new KeyRange[] { getKeyRange(KeyRange.UNBOUND, true, Ka1A, false), getKeyRange(Ka1A, true, Ka1B, false), })); // Scan range spans third, split into 3 due to concurrency config. testCases.addAll( foreach( new KeyRange[][] { {getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("a"), true)}, {getKeyRange(Bytes.toBytes("1"), true, Bytes.toBytes("1"), true)}, {getKeyRange(Bytes.toBytes("B"), true, Bytes.toBytes("E"), false)} }, new int[] {1, 1, 1}, new KeyRange[] { getKeyRange(Ka1B, true, Ka1C, false), getKeyRange(Ka1C, true, Ka1D, false), getKeyRange(Ka1D, true, Ka1E, false), })); // Scan range spans third, split into 3 due to concurrency config. testCases.addAll( foreach( new KeyRange[][] { {getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("a"), true)}, {getKeyRange(Bytes.toBytes("1"), true, Bytes.toBytes("1"), true)}, {getKeyRange(Bytes.toBytes("B"), true, Bytes.toBytes("E"), false)} }, new int[] {1, 1, 1}, new KeyRange[] { getKeyRange(Ka1B, true, Ka1C, false), getKeyRange(Ka1C, true, Ka1D, false), getKeyRange(Ka1D, true, Ka1E, false), })); // Scan range spans 2 ranges, split into 4 due to concurrency config. testCases.addAll( foreach( new KeyRange[][] { {getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("a"), true)}, {getKeyRange(Bytes.toBytes("1"), true, Bytes.toBytes("1"), true)}, {getKeyRange(Bytes.toBytes("F"), true, Bytes.toBytes("H"), false)} }, new int[] {1, 1, 1}, new KeyRange[] { getKeyRange(Ka1E, true, Ka1F, false), getKeyRange(Ka1F, true, Ka1G, false), getKeyRange(Ka1G, true, Ka1H, false), getKeyRange(Ka1H, true, Ka1I, false), })); // Scan range spans more than 3 range, no split. testCases.addAll( foreach( new KeyRange[][] { { getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("a"), true), getKeyRange(Bytes.toBytes("b"), true, Bytes.toBytes("b"), true) }, { getKeyRange(Bytes.toBytes("1"), true, Bytes.toBytes("1"), true), getKeyRange(Bytes.toBytes("2"), true, Bytes.toBytes("2"), true), }, { getKeyRange(Bytes.toBytes("A"), true, Bytes.toBytes("A"), true), getKeyRange(Bytes.toBytes("C"), true, Bytes.toBytes("D"), true), getKeyRange(Bytes.toBytes("G"), true, Bytes.toBytes("G"), true) } }, new int[] {1, 1, 1}, new KeyRange[] { getKeyRange(Ka1A, true, Ka1B, false), getKeyRange(Ka1B, true, Ka1E, false), getKeyRange(Ka1G, true, Ka1I, false), getKeyRange(Ka2A, true, KeyRange.UNBOUND, false) })); return testCases; }
@Parameters(name = "{0} {1} {2}") public static Collection<Object> data() { List<Object> testCases = Lists.newArrayList(); testCases.addAll( foreach( new KeyRange[][] { { KeyRange.getKeyRange(Bytes.toBytes("aaa"), true, Bytes.toBytes("aaa"), true), KeyRange.getKeyRange(Bytes.toBytes("aac"), true, Bytes.toBytes("aad"), true), KeyRange.getKeyRange(Bytes.toBytes("abc"), true, Bytes.toBytes("def"), true) } }, new int[] {3}, new SeekNext("aab", "aac"), new SeekNext("abb", "abc"), new Include("abc"), new Include("abe"), new Include("def"), new Finished("deg"))); testCases.addAll( foreach( new KeyRange[][] { { KeyRange.getKeyRange(Bytes.toBytes("aaa"), true, Bytes.toBytes("aaa"), true), KeyRange.getKeyRange(Bytes.toBytes("abc"), false, Bytes.toBytes("def"), true) } }, new int[] {3}, new SeekNext("aba", "abd"), new Include("abe"), new Include("def"), new Finished("deg"))); testCases.addAll( foreach( new KeyRange[][] { { KeyRange.getKeyRange(Bytes.toBytes("aaa"), true, Bytes.toBytes("aaa"), true), KeyRange.getKeyRange(Bytes.toBytes("abc"), false, Bytes.toBytes("def"), false) } }, new int[] {3}, new SeekNext("aba", "abd"), new Finished("def"))); testCases.addAll( foreach( new KeyRange[][] { { KeyRange.getKeyRange(Bytes.toBytes("abc"), true, Bytes.toBytes("def"), true), KeyRange.getKeyRange(Bytes.toBytes("dzy"), false, Bytes.toBytes("xyz"), false), } }, new int[] {3}, new Include("def"), new SeekNext("deg", "dzz"), new Include("eee"), new Finished("xyz"))); testCases.addAll( foreach( new KeyRange[][] { { KeyRange.getKeyRange(Bytes.toBytes("aaa"), true, Bytes.toBytes("aaa"), true), KeyRange.getKeyRange(Bytes.toBytes("abc"), true, Bytes.toBytes("abc"), true), KeyRange.getKeyRange(Bytes.toBytes("def"), true, Bytes.toBytes("def"), true), }, { KeyRange.getKeyRange(Bytes.toBytes("AB"), true, Bytes.toBytes("AX"), true), KeyRange.getKeyRange(Bytes.toBytes("EA"), false, Bytes.toBytes("EZ"), false), KeyRange.getKeyRange(Bytes.toBytes("PO"), true, Bytes.toBytes("PP"), false), } }, new int[] {3, 2}, new Include("abcAB"), new SeekNext("abcAY", "abcEB"), new Include("abcEF"), new SeekNext("abcPP", "defAB"), new SeekNext("defEZ", "defPO"), new Include("defPO"), new Finished("defPP"))); testCases.addAll( foreach( new KeyRange[][] { { KeyRange.getKeyRange(Bytes.toBytes("AB"), true, Bytes.toBytes("AX"), true), KeyRange.getKeyRange(Bytes.toBytes("EA"), false, Bytes.toBytes("EZ"), false), KeyRange.getKeyRange(Bytes.toBytes("PO"), true, Bytes.toBytes("PP"), false), }, { KeyRange.getKeyRange(Bytes.toBytes("abc"), true, Bytes.toBytes("abc"), true), KeyRange.getKeyRange(Bytes.toBytes("def"), true, Bytes.toBytes("def"), true), } }, new int[] {2, 3}, new Include("ABabc"), new SeekNext("ABdeg", "ACabc"), new Include("AMabc"), new SeekNext("AYabc", "EBabc"), new Include("EFabc"), new SeekNext("EZdef", "POabc"), new SeekNext("POabd", "POdef"), new Include("POdef"), new Finished("PPabc"))); testCases.addAll( foreach( new KeyRange[][] { { KeyRange.getKeyRange(Bytes.toBytes("PO"), true, Bytes.toBytes("PP"), false), }, { KeyRange.getKeyRange(Bytes.toBytes("def"), true, Bytes.toBytes("def"), true), } }, new int[] {2, 3}, new Include("POdef"), new Finished("POdeg"))); testCases.addAll( foreach( new KeyRange[][] { { KeyRange.getKeyRange(Bytes.toBytes("PO"), true, Bytes.toBytes("PO"), true), }, { KeyRange.getKeyRange(Bytes.toBytes("def"), true, Bytes.toBytes("def"), true), } }, new int[] {2, 3}, new Include("POdef"), new Finished("PPdef"))); testCases.addAll( foreach( new KeyRange[][] { { KeyRange.getKeyRange(Bytes.toBytes("abc"), true, Bytes.toBytes("def"), true), KeyRange.getKeyRange(Bytes.toBytes("dzy"), false, Bytes.toBytes("xyz"), false), }, { KeyRange.getKeyRange(Bytes.toBytes("AA"), true, Bytes.toBytes("AB"), false), }, { KeyRange.getKeyRange(Bytes.toBytes("AA"), true, Bytes.toBytes("AB"), false), }, { KeyRange.getKeyRange(Bytes.toBytes("AA"), true, Bytes.toBytes("AB"), false), }, { KeyRange.getKeyRange(Bytes.toBytes("AA"), true, Bytes.toBytes("AB"), false), } }, new int[] {3, 2, 2, 2, 2}, new SeekNext("abcABABABAB", "abdAAAAAAAA"), new SeekNext("defAAABABAB", "dzzAAAAAAAA"), new Finished("xyyABABABAB"))); testCases.addAll( foreach( new KeyRange[][] { { KeyRange.getKeyRange(Bytes.toBytes("AAA"), true, Bytes.toBytes("AAA"), true), KeyRange.getKeyRange(Bytes.toBytes("abc"), true, Bytes.toBytes("def"), true), KeyRange.getKeyRange(Bytes.toBytes("dzy"), false, Bytes.toBytes("xyz"), false), }, { KeyRange.getKeyRange(Bytes.toBytes("AB"), true, Bytes.toBytes("AX"), true), KeyRange.getKeyRange(Bytes.toBytes("EA"), false, Bytes.toBytes("EZ"), false), KeyRange.getKeyRange(Bytes.toBytes("PO"), true, Bytes.toBytes("PP"), false), } }, new int[] {3, 2}, new SeekNext("aaaAA", "abcAB"), new SeekNext("abcZZ", "abdAB"), new SeekNext("abdZZ", "abeAB"), new SeekNext( new byte[] {'d', 'e', 'a', (byte) 0xFF, (byte) 0xFF}, new byte[] {'d', 'e', 'b', 'A', 'B'}), new Include("defAB"), new Include("defAC"), new Include("defAW"), new Include("defAX"), new Include("defEB"), new Include("defPO"), new SeekNext("degAB", "dzzAB"), new Include("dzzAX"), new Include("dzzEY"), new SeekNext("dzzEZ", "dzzPO"), new Include("eeeAB"), new Include("eeeAC"), new SeekNext("eeeEA", "eeeEB"), new Include("eeeEF"), new SeekNext("eeeEZ", "eeePO"), new Finished("xyzAA"))); testCases.addAll( foreach( new KeyRange[][] { { KeyRange.getKeyRange(Bytes.toBytes("aaa"), true, Bytes.toBytes("aaa"), true), KeyRange.getKeyRange(Bytes.toBytes("abc"), true, Bytes.toBytes("def"), true), KeyRange.getKeyRange(Bytes.toBytes("dzz"), true, Bytes.toBytes("xyz"), false), } }, new int[] {3}, new SeekNext("abb", "abc"), new Include("abc"), new Include("abe"), new Finished("xyz"))); testCases.addAll( foreach( new KeyRange[][] { { KeyRange.getKeyRange(Bytes.toBytes("abc"), true, Bytes.toBytes("def"), true), KeyRange.getKeyRange(Bytes.toBytes("dzy"), false, Bytes.toBytes("xyz"), false), }, { KeyRange.getKeyRange(Bytes.toBytes("AB"), true, Bytes.toBytes("AX"), true), KeyRange.getKeyRange(Bytes.toBytes("EA"), false, Bytes.toBytes("EZ"), false), KeyRange.getKeyRange(Bytes.toBytes("PO"), true, Bytes.toBytes("PP"), false), }, { KeyRange.getKeyRange(Bytes.toBytes("100"), true, Bytes.toBytes("250"), false), KeyRange.getKeyRange(Bytes.toBytes("700"), false, Bytes.toBytes("901"), false), } }, new int[] {3, 2, 3}, new SeekNext("abcEB700", "abcEB701"), new Include("abcEB701"), new SeekNext("dzzAB250", "dzzAB701"), new Finished("zzzAA000"))); // TODO variable length columns // testCases.addAll( // foreach(new KeyRange[][]{{ // KeyRange.getKeyRange(Bytes.toBytes("apple"), true, Bytes.toBytes("lemon"), // true), // KeyRange.getKeyRange(Bytes.toBytes("pear"), false, Bytes.toBytes("yam"), // false), // }, // { // KeyRange.getKeyRange(Bytes.toBytes("AB"), true, Bytes.toBytes("AX"), // true), // KeyRange.getKeyRange(Bytes.toBytes("EA"), false, Bytes.toBytes("EZ"), // false), // KeyRange.getKeyRange(Bytes.toBytes("PO"), true, Bytes.toBytes("PP"), // false), // }, // { // KeyRange.getKeyRange(Bytes.toBytes("100"), true, Bytes.toBytes("250"), // false), // KeyRange.getKeyRange(Bytes.toBytes("700"), false, Bytes.toBytes("901"), // false), // }}, // new int[]{3,3}) // ); return testCases; }