Return-Path: X-Original-To: apmail-kudu-commits-archive@minotaur.apache.org Delivered-To: apmail-kudu-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 0459618626 for ; Fri, 1 Apr 2016 20:07:46 +0000 (UTC) Received: (qmail 18333 invoked by uid 500); 1 Apr 2016 20:07:46 -0000 Delivered-To: apmail-kudu-commits-archive@kudu.apache.org Received: (qmail 18314 invoked by uid 500); 1 Apr 2016 20:07:45 -0000 Mailing-List: contact commits-help@kudu.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@kudu.incubator.apache.org Delivered-To: mailing list commits@kudu.incubator.apache.org Received: (qmail 18305 invoked by uid 99); 1 Apr 2016 20:07:45 -0000 Received: from pnap-us-west-generic-nat.apache.org (HELO spamd3-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 01 Apr 2016 20:07:45 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd3-us-west.apache.org (ASF Mail Server at spamd3-us-west.apache.org) with ESMTP id 5C424180225 for ; Fri, 1 Apr 2016 20:07:45 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd3-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: -3.221 X-Spam-Level: X-Spam-Status: No, score=-3.221 tagged_above=-999 required=6.31 tests=[KAM_ASCII_DIVIDERS=0.8, KAM_LAZY_DOMAIN_SECURITY=1, RCVD_IN_DNSWL_HI=-5, RCVD_IN_MSPIKE_H3=-0.01, RCVD_IN_MSPIKE_WL=-0.01, RP_MATCHES_RCVD=-0.001] autolearn=disabled Received: from mx2-lw-eu.apache.org ([10.40.0.8]) by localhost (spamd3-us-west.apache.org [10.40.0.10]) (amavisd-new, port 10024) with ESMTP id GCUIvhz_aymh for ; Fri, 1 Apr 2016 20:07:34 +0000 (UTC) Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx2-lw-eu.apache.org (ASF Mail Server at mx2-lw-eu.apache.org) with SMTP id E65BD5FB03 for ; Fri, 1 Apr 2016 20:07:31 +0000 (UTC) Received: (qmail 17227 invoked by uid 99); 1 Apr 2016 20:07:31 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 01 Apr 2016 20:07:31 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 085D1DFFF8; Fri, 1 Apr 2016 20:07:31 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: danburkert@apache.org To: commits@kudu.incubator.apache.org Date: Fri, 01 Apr 2016 20:07:32 -0000 Message-Id: <3ad55c2a174f4390aa6147fef8dd43d9@git.apache.org> In-Reply-To: <9a77317690d0487b9aec158953c365fe@git.apache.org> References: <9a77317690d0487b9aec158953c365fe@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [3/3] incubator-kudu git commit: [java-client] implement KuduPredicate API [java-client] implement KuduPredicate API This commit adds a new class KuduPredicate, which corresponds to the new ColumnPredicatePB predicate type for scans. The KuduPredicate allows specifying exclusive upper bound and lower bound predicates on columns. This commit is a first step towards predicate optimization and partition pruning, but stops short of actually implementing those optimizations. This commit does not attempt to change any callers of the now deprecated ColumnRangePredicate API. Change-Id: Icdca28139a2f4f15633cfd872e372429bad831cd Reviewed-on: http://gerrit.cloudera.org:8080/2591 Tested-by: Kudu Jenkins Reviewed-by: Jean-Daniel Cryans Project: http://git-wip-us.apache.org/repos/asf/incubator-kudu/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-kudu/commit/16c03cda Tree: http://git-wip-us.apache.org/repos/asf/incubator-kudu/tree/16c03cda Diff: http://git-wip-us.apache.org/repos/asf/incubator-kudu/diff/16c03cda Branch: refs/heads/master Commit: 16c03cda041f901247d83bf37c1958697a3a96dc Parents: dc58069 Author: Dan Burkert Authored: Fri Mar 4 16:00:55 2016 -0800 Committer: Dan Burkert Committed: Fri Apr 1 19:56:59 2016 +0000 ---------------------------------------------------------------------- .../client/AbstractKuduScannerBuilder.java | 35 +- .../org/kududb/client/AsyncKuduScanner.java | 63 +- .../src/main/java/org/kududb/client/Bytes.java | 40 +- .../org/kududb/client/ColumnRangePredicate.java | 68 +- .../java/org/kududb/client/KuduPredicate.java | 678 +++++++++++++++++++ .../java/org/kududb/client/KuduScanner.java | 2 +- .../java/org/kududb/client/ProtobufHelper.java | 31 +- .../java/org/kududb/client/BaseKuduTest.java | 9 +- .../test/java/org/kududb/client/TestBytes.java | 8 + .../java/org/kududb/client/TestKuduClient.java | 51 ++ .../org/kududb/client/TestKuduPredicate.java | 628 +++++++++++++++++ .../org/kududb/client/TestScanPredicate.java | 602 ++++++++++++++++ .../src/test/resources/log4j.properties | 1 - 13 files changed, 2154 insertions(+), 62 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/16c03cda/java/kudu-client/src/main/java/org/kududb/client/AbstractKuduScannerBuilder.java ---------------------------------------------------------------------- diff --git a/java/kudu-client/src/main/java/org/kududb/client/AbstractKuduScannerBuilder.java b/java/kudu-client/src/main/java/org/kududb/client/AbstractKuduScannerBuilder.java index c4c17ef..ae65aaf 100644 --- a/java/kudu-client/src/main/java/org/kududb/client/AbstractKuduScannerBuilder.java +++ b/java/kudu-client/src/main/java/org/kududb/client/AbstractKuduScannerBuilder.java @@ -16,8 +16,9 @@ // under the License. package org.kududb.client; -import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import com.google.common.collect.ImmutableList; import org.kududb.annotations.InterfaceAudience; @@ -34,7 +35,9 @@ public abstract class AbstractKuduScannerBuilder , T> { final AsyncKuduClient client; final KuduTable table; - final List columnRangePredicates; + + /** Map of column name to predicate */ + final Map predicates = new HashMap<>(); AsyncKuduScanner.ReadMode readMode = AsyncKuduScanner.ReadMode.READ_LATEST; int batchSizeBytes = 1024*1024; @@ -53,7 +56,6 @@ public abstract class AbstractKuduScannerBuilder AbstractKuduScannerBuilder(AsyncKuduClient client, KuduTable table) { this.client = client; this.table = table; - this.columnRangePredicates = new ArrayList<>(); this.scanRequestTimeout = client.getDefaultOperationTimeoutMs(); } @@ -71,10 +73,11 @@ public abstract class AbstractKuduScannerBuilder * Adds a predicate for a column. * @param predicate predicate for a column to add * @return this instance + * @deprecated use {@link #addPredicate(KuduPredicate)} */ + @Deprecated public S addColumnRangePredicate(ColumnRangePredicate predicate) { - columnRangePredicates.add(predicate.getPb()); - return (S) this; + return addPredicate(predicate.toKuduPredicate()); } /** @@ -85,9 +88,25 @@ public abstract class AbstractKuduScannerBuilder * @throws IllegalArgumentException thrown when the passed bytes aren't valid */ public S addColumnRangePredicatesRaw(byte[] predicateBytes) { - List predicates = - ColumnRangePredicate.fromByteArray(predicateBytes); - columnRangePredicates.addAll(predicates); + for (Tserver.ColumnRangePredicatePB pb : ColumnRangePredicate.fromByteArray(predicateBytes)) { + addPredicate(ColumnRangePredicate.fromPb(pb).toKuduPredicate()); + } + return (S) this; + } + + /** + * Adds a predicate to the scan. + * @param predicate predicate to add + * @return this instance + */ + public S addPredicate(KuduPredicate predicate) { + String columnName = predicate.getColumn().getName(); + KuduPredicate existing = predicates.get(columnName); + if (existing == null) { + predicates.put(columnName, predicate); + } else { + predicates.put(columnName, existing.merge(predicate)); + } return (S) this; } http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/16c03cda/java/kudu-client/src/main/java/org/kududb/client/AsyncKuduScanner.java ---------------------------------------------------------------------- diff --git a/java/kudu-client/src/main/java/org/kududb/client/AsyncKuduScanner.java b/java/kudu-client/src/main/java/org/kududb/client/AsyncKuduScanner.java index 33c4f73..cabca8d 100644 --- a/java/kudu-client/src/main/java/org/kududb/client/AsyncKuduScanner.java +++ b/java/kudu-client/src/main/java/org/kududb/client/AsyncKuduScanner.java @@ -25,27 +25,33 @@ */ package org.kududb.client; -import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.List; - +import com.google.common.collect.ImmutableList; import com.google.protobuf.Message; import com.google.protobuf.ZeroCopyLiteralByteString; +import com.stumbleupon.async.Callback; +import com.stumbleupon.async.Deferred; +import org.jboss.netty.buffer.ChannelBuffer; import org.kududb.ColumnSchema; import org.kududb.Common; import org.kududb.Schema; -import com.stumbleupon.async.Callback; -import com.stumbleupon.async.Deferred; import org.kududb.annotations.InterfaceAudience; import org.kududb.annotations.InterfaceStability; import org.kududb.tserver.Tserver; import org.kududb.util.Pair; -import org.jboss.netty.buffer.ChannelBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + import static com.google.common.base.Preconditions.checkArgument; -import static org.kududb.tserver.Tserver.*; +import static org.kududb.tserver.Tserver.NewScanRequestPB; +import static org.kududb.tserver.Tserver.ScanRequestPB; +import static org.kududb.tserver.Tserver.ScanResponsePB; +import static org.kududb.tserver.Tserver.TabletServerErrorPB; /** * Creates a scanner to read data from Kudu. @@ -133,7 +139,11 @@ public final class AsyncKuduScanner { private final AsyncKuduClient client; private final KuduTable table; private final Schema schema; - private final List columnRangePredicates; + + /** + * Map of column name to predicate. + */ + private final Map predicates; /** * Maximum number of bytes returned by the scanner, on each batch. @@ -220,7 +230,7 @@ public final class AsyncKuduScanner { AsyncKuduScanner(AsyncKuduClient client, KuduTable table, List projectedNames, List projectedIndexes, ReadMode readMode, long scanRequestTimeout, - List columnRangePredicates, long limit, + Map predicates, long limit, boolean cacheBlocks, boolean prefetching, byte[] startPrimaryKey, byte[] endPrimaryKey, byte[] startPartitionKey, byte[] endPartitionKey, @@ -240,7 +250,7 @@ public final class AsyncKuduScanner { this.table = table; this.readMode = readMode; this.scanRequestTimeout = scanRequestTimeout; - this.columnRangePredicates = columnRangePredicates; + this.predicates = predicates; this.limit = limit; this.cacheBlocks = cacheBlocks; this.prefetching = prefetching; @@ -314,6 +324,22 @@ public final class AsyncKuduScanner { } else { this.schema = table.getSchema(); } + + // If any of the column predicates are of type None (the predicate is known + // to match no rows), then the scan can be short circuited without + // contacting any tablet servers. + boolean shortCircuit = false; + for (KuduPredicate predicate : this.predicates.values()) { + if (predicate.getType() == KuduPredicate.PredicateType.NONE) { + shortCircuit = true; + break; + } + } + if (shortCircuit) { + LOG.debug("Short circuiting scan with predicates: {}", predicates.values()); + this.hasMore = false; + this.closed = true; + } } /** @@ -665,6 +691,15 @@ public final class AsyncKuduScanner { return "Scan"; } + @Override + Collection getRequiredFeatures() { + if (predicates.isEmpty()) { + return ImmutableList.of(); + } else { + return ImmutableList.of(Tserver.TabletServerFeatures.COLUMN_PREDICATES_VALUE); + } + } + /** Serializes this request. */ ChannelBuffer serialize(Message header) { final ScanRequestPB.Builder builder = ScanRequestPB.newBuilder(); @@ -701,8 +736,8 @@ public final class AsyncKuduScanner { newBuilder.setStopPrimaryKey(ZeroCopyLiteralByteString.copyFrom(endPrimaryKey)); } - if (!columnRangePredicates.isEmpty()) { - newBuilder.addAllDEPRECATEDRangePredicates(columnRangePredicates); + for (KuduPredicate pred : predicates.values()) { + newBuilder.addColumnPredicates(pred.toPB()); } builder.setNewScanRequest(newBuilder.build()) .setBatchSizeBytes(batchSizeBytes); @@ -791,7 +826,7 @@ public final class AsyncKuduScanner { public AsyncKuduScanner build() { return new AsyncKuduScanner( client, table, projectedColumnNames, projectedColumnIndexes, readMode, - scanRequestTimeout, columnRangePredicates, limit, cacheBlocks, + scanRequestTimeout, predicates, limit, cacheBlocks, prefetching, lowerBoundPrimaryKey, upperBoundPrimaryKey, lowerBoundPartitionKey, upperBoundPartitionKey, htTimestamp, batchSizeBytes); http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/16c03cda/java/kudu-client/src/main/java/org/kududb/client/Bytes.java ---------------------------------------------------------------------- diff --git a/java/kudu-client/src/main/java/org/kududb/client/Bytes.java b/java/kudu-client/src/main/java/org/kududb/client/Bytes.java index e327b9b..624536e 100644 --- a/java/kudu-client/src/main/java/org/kududb/client/Bytes.java +++ b/java/kudu-client/src/main/java/org/kududb/client/Bytes.java @@ -710,7 +710,7 @@ public final class Bytes { // Pretty-printing byte arrays. // // ---------------------------- // - private static final byte[] HEX = { + private static final char[] HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; @@ -741,8 +741,8 @@ public final class Bytes { outbuf.append('\\').append('t'); } else { outbuf.append("\\x") - .append((char) HEX[(b >>> 4) & 0x0F]) - .append((char) HEX[b & 0x0F]); + .append(HEX[(b >>> 4) & 0x0F]) + .append(HEX[b & 0x0F]); } } if (ascii < n / 2) { @@ -793,25 +793,27 @@ public final class Bytes { return buf.toString(); } - // This doesn't really belong here but it doesn't belong anywhere else - // either, so let's put it close to the other pretty-printing functions. /** - * Pretty-prints a {@code long} into a fixed-width hexadecimal number. - * @return A string of the form {@code 0x0123456789ABCDEF}. + * Convert a byte array to a hex encoded string. + * + * TODO: replace this with {@link com.google.common.io.BaseEncoding} + * when the Guava version is bumped. + * + * https://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to-a-hex-string-in-java + * @param bytes the bytes to encode + * @return the hex encoded bytes */ - public static String hex(long v) { - final byte[] buf = new byte[2 + 16]; - buf[0] = '0'; - buf[1] = 'x'; - int i = 2 + 16; - do { - buf[--i] = HEX[(int) v & 0x0F]; - v >>>= 4; - } while (v != 0); - for (/**/; i > 1; i--) { - buf[i] = '0'; + public static String hex(byte[] bytes) { + StringBuilder sb = new StringBuilder(2 + bytes.length * 2); + sb.append('0'); + sb.append('x'); + + for (byte b : bytes) { + int v = b & 0xFF; + sb.append(HEX[v >>> 4]); + sb.append(HEX[v & 0x0F]); } - return new String(buf); + return sb.toString(); } // Ugly stuff http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/16c03cda/java/kudu-client/src/main/java/org/kududb/client/ColumnRangePredicate.java ---------------------------------------------------------------------- diff --git a/java/kudu-client/src/main/java/org/kududb/client/ColumnRangePredicate.java b/java/kudu-client/src/main/java/org/kududb/client/ColumnRangePredicate.java index 1d0daa3..0e24088 100644 --- a/java/kudu-client/src/main/java/org/kududb/client/ColumnRangePredicate.java +++ b/java/kudu-client/src/main/java/org/kududb/client/ColumnRangePredicate.java @@ -21,10 +21,8 @@ import com.google.protobuf.ZeroCopyLiteralByteString; import org.kududb.ColumnSchema; import org.kududb.Type; import org.kududb.annotations.InterfaceAudience; -import org.kududb.annotations.InterfaceStability; import org.kududb.tserver.Tserver; -import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -32,9 +30,10 @@ import java.util.List; /** * A range predicate on one of the columns in the underlying data. * Both boundaries are inclusive. + * @deprecated use the {@link KuduPredicate} class instead. */ @InterfaceAudience.Public -@InterfaceStability.Evolving +@Deprecated public class ColumnRangePredicate { private final Tserver.ColumnRangePredicatePB.Builder pb = Tserver.ColumnRangePredicatePB @@ -63,6 +62,52 @@ public class ColumnRangePredicate { } /** + * Convert a bound into a {@link KuduPredicate}. + * @param column the column + * @param op the bound comparison operator + * @param bound the bound + * @return the {@code KuduPredicate} + */ + private static KuduPredicate toKuduPredicate(ColumnSchema column, + KuduPredicate.ComparisonOp op, + byte[] bound) { + if (bound == null) { return null; } + switch (column.getType().getDataType()) { + case BOOL: return KuduPredicate.newComparisonPredicate(column, op, Bytes.getBoolean(bound)); + case INT8: return KuduPredicate.newComparisonPredicate(column, op, Bytes.getByte(bound)); + case INT16: return KuduPredicate.newComparisonPredicate(column, op, Bytes.getShort(bound)); + case INT32: return KuduPredicate.newComparisonPredicate(column, op, Bytes.getInt(bound)); + case INT64: + case TIMESTAMP: return KuduPredicate.newComparisonPredicate(column, op, Bytes.getLong(bound)); + case FLOAT: return KuduPredicate.newComparisonPredicate(column, op, Bytes.getFloat(bound)); + case DOUBLE: return KuduPredicate.newComparisonPredicate(column, op, Bytes.getDouble(bound)); + case STRING: return KuduPredicate.newComparisonPredicate(column, op, Bytes.getString(bound)); + case BINARY: return KuduPredicate.newComparisonPredicate(column, op, bound); + default: + throw new IllegalStateException(String.format("unknown column type %s", column.getType())); + } + } + + /** + * Convert this column range predicate into a {@link KuduPredicate}. + * @return the column predicate. + */ + public KuduPredicate toKuduPredicate() { + KuduPredicate lower = + toKuduPredicate(column, KuduPredicate.ComparisonOp.GREATER_EQUAL, lowerBound); + KuduPredicate upper = + toKuduPredicate(column, KuduPredicate.ComparisonOp.LESS_EQUAL, upperBound); + + if (upper != null && lower != null) { + return upper.merge(lower); + } else if (upper != null) { + return upper; + } else { + return lower; + } + } + + /** * Set a boolean for the lower bound * @param lowerBound value for the lower bound */ @@ -301,6 +346,23 @@ public class ColumnRangePredicate { } /** + * Creates a {@code ColumnRangePredicate} from a protobuf column range predicate message. + * @param pb the protobuf message + * @return a column range predicate + */ + static ColumnRangePredicate fromPb(Tserver.ColumnRangePredicatePB pb) { + ColumnRangePredicate pred = + new ColumnRangePredicate(ProtobufHelper.pbToColumnSchema(pb.getColumn())); + if (pb.hasLowerBound()) { + pred.setLowerBoundInternal(pb.getLowerBound().toByteArray()); + } + if (pb.hasInclusiveUpperBound()) { + pred.setUpperBoundInternal(pb.getInclusiveUpperBound().toByteArray()); + } + return pred; + } + + /** * Convert a list of predicates given in bytes back to its pb format. It also hides the * InvalidProtocolBufferException. */ http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/16c03cda/java/kudu-client/src/main/java/org/kududb/client/KuduPredicate.java ---------------------------------------------------------------------- diff --git a/java/kudu-client/src/main/java/org/kududb/client/KuduPredicate.java b/java/kudu-client/src/main/java/org/kududb/client/KuduPredicate.java new file mode 100644 index 0000000..aa0801b --- /dev/null +++ b/java/kudu-client/src/main/java/org/kududb/client/KuduPredicate.java @@ -0,0 +1,678 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.kududb.client; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.primitives.UnsignedBytes; +import com.google.protobuf.ByteString; +import org.kududb.ColumnSchema; +import org.kududb.Common; +import org.kududb.Schema; +import org.kududb.Type; +import org.kududb.annotations.InterfaceAudience; +import org.kududb.annotations.InterfaceStability; + +import java.util.Arrays; + +/** + * A predicate which can be used to filter rows based on the value of a column. + */ +@InterfaceAudience.Public +@InterfaceStability.Evolving +public class KuduPredicate { + + /** + * The predicate type. + */ + enum PredicateType { + /** A predicate which filters all rows. */ + NONE, + /** A predicate which filters all rows not equal to a value. */ + EQUALITY, + /** A predicate which filters all rows not in a range. */ + RANGE, + /** A predicate which filters all null rows. */ + IS_NOT_NULL, + } + + /** + * The comparison operator of a predicate. + */ + public enum ComparisonOp { + GREATER, + GREATER_EQUAL, + EQUAL, + LESS, + LESS_EQUAL, + } + + private final PredicateType type; + private final ColumnSchema column; + + /** + * The inclusive lower bound value if this is a Range predicate, or + * the createEquality value if this is an Equality predicate. + */ + private final byte[] lower; + + /** The exclusive upper bound value if this is a Range predicate. */ + private final byte[] upper; + + /** + * Creates a new {@code KuduPredicate} on a boolean column. + * @param column the column schema + * @param op the comparison operation + * @param value the value to compare against + */ + public static KuduPredicate newComparisonPredicate(ColumnSchema column, + ComparisonOp op, + boolean value) { + checkColumn(column, Type.BOOL); + // Create the comparison predicate. Range predicates on boolean values can + // always be converted to either an equality, an IS NOT NULL (filtering only + // null values), or NONE (filtering all values). + switch (op) { + case GREATER: { + // b > true -> b NONE + // b > false -> b = true + if (value) { + return none(column); + } else { + return new KuduPredicate(PredicateType.EQUALITY, column, Bytes.fromBoolean(true), null); + } + } + case GREATER_EQUAL: { + // b >= true -> b = true + // b >= false -> b IS NOT NULL + if (value) { + return new KuduPredicate(PredicateType.EQUALITY, column, Bytes.fromBoolean(true), null); + } else { + return newIsNotNullPredicate(column); + } + } + case EQUAL: return new KuduPredicate(PredicateType.EQUALITY, column, + Bytes.fromBoolean(value), null); + case LESS: { + // b < true -> b NONE + // b < false -> b = true + if (value) { + return new KuduPredicate(PredicateType.EQUALITY, column, Bytes.fromBoolean(false), null); + } else { + return none(column); + } + } + case LESS_EQUAL: { + // b <= true -> b IS NOT NULL + // b <= false -> b = false + if (value) { + return newIsNotNullPredicate(column); + } else { + return new KuduPredicate(PredicateType.EQUALITY, column, Bytes.fromBoolean(false), null); + } + } + default: throw new RuntimeException("unknown comparison op"); + } + } + + /** + * Creates a new comparison predicate on an integer or timestamp column. + * @param column the column schema + * @param op the comparison operation + * @param value the value to compare against + */ + public static KuduPredicate newComparisonPredicate(ColumnSchema column, + ComparisonOp op, + long value) { + checkColumn(column, Type.INT8, Type.INT16, Type.INT32, Type.INT64, Type.TIMESTAMP); + Preconditions.checkArgument(value <= maxIntValue(column.getType()) && + value >= minIntValue(column.getType()), + "integer value out of range for %s column: %s", + column.getType(), value); + + if (op == ComparisonOp.LESS_EQUAL) { + if (value == maxIntValue(column.getType())) { + // If the value can't be incremented because it is at the top end of the + // range, then substitute the predicate with an IS NOT NULL predicate. + // This has the same effect as an inclusive upper bound on the maximum + // value. If the column is not nullable then the IS NOT NULL predicate + // is ignored. + return newIsNotNullPredicate(column); + } + value += 1; + } else if (op == ComparisonOp.GREATER) { + if (value == maxIntValue(column.getType())) { + return none(column); + } + value += 1; + } + + byte[] bytes; + switch (column.getType()) { + case INT8: { + bytes = new byte[] { (byte) value }; + break; + } + case INT16: { + bytes = Bytes.fromShort((short) value); + break; + } + case INT32: { + bytes = Bytes.fromInt((int) value); + break; + } + case INT64: + case TIMESTAMP: { + bytes = Bytes.fromLong(value); + break; + } + default: throw new RuntimeException("already checked"); + } + switch (op) { + case GREATER: + case GREATER_EQUAL: return new KuduPredicate(PredicateType.RANGE, column, bytes, null); + case EQUAL: return new KuduPredicate(PredicateType.EQUALITY, column, bytes, null); + case LESS: + case LESS_EQUAL: return new KuduPredicate(PredicateType.RANGE, column, null, bytes); + default: throw new RuntimeException("unknown comparison op"); + } + } + + /** + * Creates a new comparison predicate on a float column. + * @param column the column schema + * @param op the comparison operation + * @param value the value to compare against + */ + public static KuduPredicate newComparisonPredicate(ColumnSchema column, + ComparisonOp op, + float value) { + checkColumn(column, Type.FLOAT); + if (op == ComparisonOp.LESS_EQUAL) { + if (value == Float.POSITIVE_INFINITY) { + return newIsNotNullPredicate(column); + } + value = Math.nextAfter(value, Float.POSITIVE_INFINITY); + } else if (op == ComparisonOp.GREATER) { + if (value == Float.POSITIVE_INFINITY) { + return none(column); + } + value = Math.nextAfter(value, Float.POSITIVE_INFINITY); + } + + byte[] bytes = Bytes.fromFloat(value); + switch (op) { + case GREATER: + case GREATER_EQUAL: return new KuduPredicate(PredicateType.RANGE, column, bytes, null); + case EQUAL: return new KuduPredicate(PredicateType.EQUALITY, column, bytes, null); + case LESS: + case LESS_EQUAL: return new KuduPredicate(PredicateType.RANGE, column, null, bytes); + default: throw new RuntimeException("unknown comparison op"); + } + } + + /** + * Creates a new comparison predicate on a double column. + * @param column the column schema + * @param op the comparison operation + * @param value the value to compare against + */ + public static KuduPredicate newComparisonPredicate(ColumnSchema column, + ComparisonOp op, + double value) { + checkColumn(column, Type.DOUBLE); + if (op == ComparisonOp.LESS_EQUAL) { + if (value == Double.POSITIVE_INFINITY) { + return newIsNotNullPredicate(column); + } + value = Math.nextAfter(value, Double.POSITIVE_INFINITY); + } else if (op == ComparisonOp.GREATER) { + if (value == Double.POSITIVE_INFINITY) { + return none(column); + } + value = Math.nextAfter(value, Double.POSITIVE_INFINITY); + } + + byte[] bytes = Bytes.fromDouble(value); + switch (op) { + case GREATER: + case GREATER_EQUAL: return new KuduPredicate(PredicateType.RANGE, column, bytes, null); + case EQUAL: return new KuduPredicate(PredicateType.EQUALITY, column, bytes, null); + case LESS: + case LESS_EQUAL: return new KuduPredicate(PredicateType.RANGE, column, null, bytes); + default: throw new RuntimeException("unknown comparison op"); + } + } + + /** + * Creates a new comparison predicate on a string column. + * @param column the column schema + * @param op the comparison operation + * @param value the value to compare against + */ + public static KuduPredicate newComparisonPredicate(ColumnSchema column, + ComparisonOp op, + String value) { + checkColumn(column, Type.STRING); + + byte[] bytes = Bytes.fromString(value); + if (op == ComparisonOp.LESS_EQUAL || op == ComparisonOp.GREATER) { + bytes = Arrays.copyOf(bytes, bytes.length + 1); + } + + switch (op) { + case GREATER: + case GREATER_EQUAL: return new KuduPredicate(PredicateType.RANGE, column, bytes, null); + case EQUAL: return new KuduPredicate(PredicateType.EQUALITY, column, bytes, null); + case LESS: + case LESS_EQUAL: return new KuduPredicate(PredicateType.RANGE, column, null, bytes); + default: throw new RuntimeException("unknown comparison op"); + } + } + + /** + * Creates a new comparison predicate on a binary column. + * @param column the column schema + * @param op the comparison operation + * @param value the value to compare against + */ + public static KuduPredicate newComparisonPredicate(ColumnSchema column, + ComparisonOp op, + byte[] value) { + checkColumn(column, Type.BINARY); + + if (op == ComparisonOp.LESS_EQUAL || op == ComparisonOp.GREATER) { + value = Arrays.copyOf(value, value.length + 1); + } + + switch (op) { + case GREATER: + case GREATER_EQUAL: return new KuduPredicate(PredicateType.RANGE, column, value, null); + case EQUAL: return new KuduPredicate(PredicateType.EQUALITY, column, value, null); + case LESS: + case LESS_EQUAL: return new KuduPredicate(PredicateType.RANGE, column, null, value); + default: throw new RuntimeException("unknown comparison op"); + } + } + + /** + * @param type the predicate type + * @param column the column to which the predicate applies + * @param lower the lower bound serialized value if this is a Range predicate, + * or the equality value if this is an Equality predicate + * @param upper the upper bound serialized value if this is an Equality predicate + */ + @VisibleForTesting + KuduPredicate(PredicateType type, ColumnSchema column, byte[] lower, byte[] upper) { + this.type = type; + this.column = column; + this.lower = lower; + this.upper = upper; + } + + /** + * Factory function for a {@code None} predicate. + * @param column the column to which the predicate applies + * @return a None predicate + */ + @VisibleForTesting + static KuduPredicate none(ColumnSchema column) { + return new KuduPredicate(PredicateType.NONE, column, null, null); + } + + /** + * Factory function for an {@code IS NOT NULL} predicate. + * @param column the column to which the predicate applies + * @return a {@code IS NOT NULL} predicate + */ + @VisibleForTesting + static KuduPredicate newIsNotNullPredicate(ColumnSchema column) { + return new KuduPredicate(PredicateType.IS_NOT_NULL, column, null, null); + } + + /** + * @return the type of this predicate + */ + PredicateType getType() { + return type; + } + + /** + * Merges another {@code ColumnPredicate} into this one, returning a new + * {@code ColumnPredicate} which matches the logical intersection ({@code AND}) + * of the input predicates. + * @param other the predicate to merge with this predicate + * @return a new predicate that is the logical intersection + */ + KuduPredicate merge(KuduPredicate other) { + Preconditions.checkArgument(column.equals(other.column), + "predicates from different columns may not be merged"); + if (type == PredicateType.NONE || other.type == PredicateType.NONE) { + return none(column); + } + + if (type == PredicateType.IS_NOT_NULL) { + // NOT NULL is less selective than all other predicate types, so the + // intersection of NOT NULL with any other predicate is just the other + // predicate. + // + // Note: this will no longer be true when an IS NULL predicate type is + // added. + return other; + } + + if (type == PredicateType.EQUALITY) { + if (other.type == PredicateType.EQUALITY) { + if (compare(lower, other.lower) != 0) { + return none(this.column); + } else { + return this; + } + } else { + if ((other.lower == null || compare(lower, other.lower) >= 0) && + (other.upper == null || compare(lower, other.upper) < 0)) { + return this; + } else { + return none(this.column); + } + } + } else { + if (other.type == PredicateType.EQUALITY) { + return other.merge(this); + } else { + byte[] newLower = other.lower == null || + (lower != null && compare(lower, other.lower) >= 0) ? lower : other.lower; + byte[] newUpper = other.upper == null || + (upper != null && compare(upper, other.upper) <= 0) ? upper : other.upper; + if (newLower != null && newUpper != null && compare(newLower, newUpper) >= 0) { + return none(column); + } else { + if (newLower != null && newUpper != null && areConsecutive(newLower, newUpper)) { + return new KuduPredicate(PredicateType.EQUALITY, column, newLower, null); + } else { + return new KuduPredicate(PredicateType.RANGE, column, newLower, newUpper); + } + } + } + } + } + + /** + * @return the schema of the predicate column + */ + ColumnSchema getColumn() { + return column; + } + + /** + * Convert the predicate to the protobuf representation. + * @return the protobuf message for this predicate. + */ + Common.ColumnPredicatePB toPB() { + Common.ColumnPredicatePB.Builder builder = Common.ColumnPredicatePB.newBuilder(); + builder.setColumn(column.getName()); + + switch (type) { + case EQUALITY: { + builder.getEqualityBuilder().setValue(ByteString.copyFrom(lower)); + break; + } + case RANGE: { + Common.ColumnPredicatePB.Range.Builder b = builder.getRangeBuilder(); + if (lower != null) { + b.setLower(ByteString.copyFrom(lower)); + } + if (upper != null) { + b.setUpper(ByteString.copyFrom(upper)); + } + break; + } + case IS_NOT_NULL: { + builder.setIsNotNull(builder.getIsNotNullBuilder()); + break; + } + case NONE: throw new IllegalStateException( + "can not convert None predicate to protobuf message"); + default: throw new IllegalArgumentException( + String.format("unknown predicate type: %s", type)); + } + return builder.build(); + } + + /** + * Convert a column predicate protobuf message into a predicate. + * @return a predicate + */ + static KuduPredicate fromPB(Schema schema, Common.ColumnPredicatePB pb) { + ColumnSchema column = schema.getColumn(pb.getColumn()); + switch (pb.getPredicateCase()) { + case EQUALITY: { + return new KuduPredicate(PredicateType.EQUALITY, column, + pb.getEquality().getValue().toByteArray(), null); + + } + case RANGE: { + Common.ColumnPredicatePB.Range range = pb.getRange(); + return new KuduPredicate(PredicateType.RANGE, column, + range.hasLower() ? range.getLower().toByteArray() : null, + range.hasUpper() ? range.getUpper().toByteArray() : null); + } + default: throw new IllegalArgumentException("unknown predicate type"); + } + } + + /** + * Compares two bounds based on the type of this predicate's column. + * @param a the first serialized value + * @param b the second serialized value + * @return the comparison of the serialized values based on the column type + */ + private int compare(byte[] a, byte[] b) { + switch (column.getType().getDataType()) { + case BOOL: + return Boolean.compare(Bytes.getBoolean(a), Bytes.getBoolean(b)); + case INT8: + return Byte.compare(Bytes.getByte(a), Bytes.getByte(b)); + case INT16: + return Short.compare(Bytes.getShort(a), Bytes.getShort(b)); + case INT32: + return Integer.compare(Bytes.getInt(a), Bytes.getInt(b)); + case INT64: + case TIMESTAMP: + return Long.compare(Bytes.getLong(a), Bytes.getLong(b)); + case FLOAT: + return Float.compare(Bytes.getFloat(a), Bytes.getFloat(b)); + case DOUBLE: + return Double.compare(Bytes.getDouble(a), Bytes.getDouble(b)); + case STRING: + case BINARY: + return UnsignedBytes.lexicographicalComparator().compare(a, b); + default: + throw new IllegalStateException(String.format("unknown column type %s", column.getType())); + } + } + + /** + * Returns true if increment(a) == b. + * @param a the value which would be incremented + * @param b the target value + * @return true if increment(a) == b + */ + private boolean areConsecutive(byte[] a, byte[] b) { + switch (column.getType().getDataType()) { + case BOOL: return false; + case INT8: { + byte m = Bytes.getByte(a); + byte n = Bytes.getByte(b); + return m < n && m + 1 == n; + } + case INT16: { + short m = Bytes.getShort(a); + short n = Bytes.getShort(b); + return m < n && m + 1 == n; + } + case INT32: { + int m = Bytes.getInt(a); + int n = Bytes.getInt(b); + return m < n && m + 1 == n; + } + case INT64: + case TIMESTAMP: { + long m = Bytes.getLong(a); + long n = Bytes.getLong(b); + return m < n && m + 1 == n; + } + case FLOAT: { + float m = Bytes.getFloat(a); + float n = Bytes.getFloat(b); + return m < n && Math.nextAfter(m, Float.POSITIVE_INFINITY) == n; + } + case DOUBLE: { + double m = Bytes.getDouble(a); + double n = Bytes.getDouble(b); + return m < n && Math.nextAfter(m, Double.POSITIVE_INFINITY) == n; + } + case STRING: + case BINARY: { + if (a.length + 1 != b.length || b[a.length] != 0) return false; + for (int i = 0; i < a.length; i++) { + if (a[i] != b[i]) return false; + } + return true; + } + default: + throw new IllegalStateException(String.format("unknown column type %s", column.getType())); + } + } + + /** + * Returns the maximum value for the integer type. + * @param type an integer type + * @return the maximum value + */ + @VisibleForTesting + static long maxIntValue(Type type) { + switch (type) { + case INT8: return Byte.MAX_VALUE; + case INT16: return Short.MAX_VALUE; + case INT32: return Integer.MAX_VALUE; + case TIMESTAMP: + case INT64: return Long.MAX_VALUE; + default: throw new IllegalArgumentException("type must be an integer type"); + } + } + + /** + * Returns the minimum value for the integer type. + * @param type an integer type + * @return the minimum value + */ + @VisibleForTesting + static long minIntValue(Type type) { + switch (type) { + case INT8: return Byte.MIN_VALUE; + case INT16: return Short.MIN_VALUE; + case INT32: return Integer.MIN_VALUE; + case TIMESTAMP: + case INT64: return Long.MIN_VALUE; + default: throw new IllegalArgumentException("type must be an integer type"); + } + } + + + /** + * Checks that the column is one of the expected types. + * @param column the column being checked + * @param passedTypes the expected types (logical OR) + */ + private static void checkColumn(ColumnSchema column, Type... passedTypes) { + for (Type type : passedTypes) { + if (column.getType().equals(type)) return; + } + throw new IllegalArgumentException(String.format("%s's type isn't %s, it's %s", + column.getName(), Arrays.toString(passedTypes), + column.getType().getName())); + } + + /** + * Returns the string value of serialized value according to the type of column. + * @param value the value + * @return the text representation of the value + */ + private String valueToString(byte[] value) { + switch (column.getType().getDataType()) { + case BOOL: return Boolean.toString(Bytes.getBoolean(value)); + case INT8: return Byte.toString(Bytes.getByte(value)); + case INT16: return Short.toString(Bytes.getShort(value)); + case INT32: return Integer.toString(Bytes.getInt(value)); + case INT64: return Long.toString(Bytes.getLong(value)); + case TIMESTAMP: return RowResult.timestampToString(Bytes.getLong(value)); + case FLOAT: return Float.toString(Bytes.getFloat(value)); + case DOUBLE: return Double.toString(Bytes.getDouble(value)); + case STRING: { + String v = Bytes.getString(value); + StringBuilder sb = new StringBuilder(2 + v.length()); + sb.append('"'); + sb.append(v); + sb.append('"'); + return sb.toString(); + } + case BINARY: return Bytes.hex(value); + default: + throw new IllegalStateException(String.format("unknown column type %s", column.getType())); + } + } + + @Override + public String toString() { + switch (type) { + case EQUALITY: return String.format("`%s` = %s", column.getName(), valueToString(lower)); + case RANGE: { + if (lower == null) { + return String.format("`%s` < %s", column.getName(), valueToString(upper)); + } else if (upper == null) { + return String.format("`%s` >= %s", column.getName(), valueToString(lower)); + } else { + return String.format("`%s` >= %s AND `%s` < %s", + column.getName(), valueToString(lower), + column.getName(), valueToString(upper)); + } + } + case IS_NOT_NULL: return String.format("`%s` IS NOT NULL", column.getName()); + case NONE: return String.format("`%s` NONE", column.getName()); + default: throw new IllegalArgumentException(String.format("unknown predicate type %s", type)); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + KuduPredicate that = (KuduPredicate) o; + return type == that.type && + column.equals(that.column) && + Arrays.equals(lower, that.lower) && + Arrays.equals(upper, that.upper); + } + + @Override + public int hashCode() { + return Objects.hashCode(type, column, Arrays.hashCode(lower), Arrays.hashCode(upper)); + } +} http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/16c03cda/java/kudu-client/src/main/java/org/kududb/client/KuduScanner.java ---------------------------------------------------------------------- diff --git a/java/kudu-client/src/main/java/org/kududb/client/KuduScanner.java b/java/kudu-client/src/main/java/org/kududb/client/KuduScanner.java index cdaaa46..4756279 100644 --- a/java/kudu-client/src/main/java/org/kududb/client/KuduScanner.java +++ b/java/kudu-client/src/main/java/org/kududb/client/KuduScanner.java @@ -130,7 +130,7 @@ public class KuduScanner { public KuduScanner build() { return new KuduScanner(new AsyncKuduScanner( client, table, projectedColumnNames, projectedColumnIndexes, readMode, - scanRequestTimeout, columnRangePredicates, limit, cacheBlocks, + scanRequestTimeout, predicates, limit, cacheBlocks, prefetching, lowerBoundPrimaryKey, upperBoundPrimaryKey, lowerBoundPartitionKey, upperBoundPartitionKey, htTimestamp, batchSizeBytes)); http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/16c03cda/java/kudu-client/src/main/java/org/kududb/client/ProtobufHelper.java ---------------------------------------------------------------------- diff --git a/java/kudu-client/src/main/java/org/kududb/client/ProtobufHelper.java b/java/kudu-client/src/main/java/org/kududb/client/ProtobufHelper.java index 4f91bd2..2b2cf64 100644 --- a/java/kudu-client/src/main/java/org/kududb/client/ProtobufHelper.java +++ b/java/kudu-client/src/main/java/org/kududb/client/ProtobufHelper.java @@ -79,24 +79,27 @@ public class ProtobufHelper { return schemaBuilder.build(); } + public static ColumnSchema pbToColumnSchema(Common.ColumnSchemaPB pb) { + Type type = Type.getTypeForDataType(pb.getType()); + Object defaultValue = pb.hasReadDefaultValue() ? + byteStringToObject(type, pb.getReadDefaultValue()) : null; + ColumnSchema.Encoding encoding = ColumnSchema.Encoding.valueOf(pb.getEncoding().name()); + ColumnSchema.CompressionAlgorithm compressionAlgorithm = + ColumnSchema.CompressionAlgorithm.valueOf(pb.getCompression().name()); + return new ColumnSchema.ColumnSchemaBuilder(pb.getName(), type) + .key(pb.getIsKey()) + .nullable(pb.getIsNullable()) + .defaultValue(defaultValue) + .encoding(encoding) + .compressionAlgorithm(compressionAlgorithm) + .build(); + } + public static Schema pbToSchema(Common.SchemaPB schema) { List columns = new ArrayList<>(schema.getColumnsCount()); List columnIds = new ArrayList<>(schema.getColumnsCount()); for (Common.ColumnSchemaPB columnPb : schema.getColumnsList()) { - Type type = Type.getTypeForDataType(columnPb.getType()); - Object defaultValue = columnPb.hasReadDefaultValue() ? byteStringToObject(type, - columnPb.getReadDefaultValue()) : null; - ColumnSchema.Encoding encoding = ColumnSchema.Encoding.valueOf(columnPb.getEncoding().name()); - ColumnSchema.CompressionAlgorithm compressionAlgorithm = - ColumnSchema.CompressionAlgorithm.valueOf(columnPb.getCompression().name()); - ColumnSchema column = new ColumnSchema.ColumnSchemaBuilder(columnPb.getName(), type) - .key(columnPb.getIsKey()) - .nullable(columnPb.getIsNullable()) - .defaultValue(defaultValue) - .encoding(encoding) - .compressionAlgorithm(compressionAlgorithm) - .build(); - columns.add(column); + columns.add(pbToColumnSchema(columnPb)); int id = columnPb.getId(); if (id < 0) { throw new IllegalArgumentException("Illegal column ID: " + id); http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/16c03cda/java/kudu-client/src/test/java/org/kududb/client/BaseKuduTest.java ---------------------------------------------------------------------- diff --git a/java/kudu-client/src/test/java/org/kududb/client/BaseKuduTest.java b/java/kudu-client/src/test/java/org/kududb/client/BaseKuduTest.java index afb4e54..7014630 100644 --- a/java/kudu-client/src/test/java/org/kududb/client/BaseKuduTest.java +++ b/java/kudu-client/src/test/java/org/kududb/client/BaseKuduTest.java @@ -164,9 +164,14 @@ public class BaseKuduTest { return counter.get(); } - protected List scanTableToStrings(KuduTable table) throws Exception { + protected List scanTableToStrings(KuduTable table, + KuduPredicate... predicates) throws Exception { List rowStrings = Lists.newArrayList(); - KuduScanner scanner = syncClient.newScannerBuilder(table).build(); + KuduScanner.KuduScannerBuilder scanBuilder = syncClient.newScannerBuilder(table); + for (KuduPredicate predicate : predicates) { + scanBuilder.addPredicate(predicate); + } + KuduScanner scanner = scanBuilder.build(); while (scanner.hasMoreRows()) { RowResultIterator rows = scanner.nextRows(); for (RowResult r : rows) { http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/16c03cda/java/kudu-client/src/test/java/org/kududb/client/TestBytes.java ---------------------------------------------------------------------- diff --git a/java/kudu-client/src/test/java/org/kududb/client/TestBytes.java b/java/kudu-client/src/test/java/org/kududb/client/TestBytes.java index 57a93da..11c2035 100644 --- a/java/kudu-client/src/test/java/org/kududb/client/TestBytes.java +++ b/java/kudu-client/src/test/java/org/kududb/client/TestBytes.java @@ -18,6 +18,7 @@ package org.kududb.client; import static org.junit.Assert.assertEquals; +import org.junit.Assert; import org.junit.Test; import java.math.BigInteger; @@ -94,4 +95,11 @@ public class TestBytes { Bytes.setDouble(bytes, aDouble); assertEquals(aDouble, Bytes.getDouble(bytes), 0.001); } + + @Test + public void testHex() { + byte[] bytes = new byte[] { (byte) 0x01, (byte) 0x23, (byte) 0x45, (byte) 0x67, + (byte) 0x89, (byte) 0xAB, (byte) 0xCD, (byte) 0xEF }; + Assert.assertEquals("0x0123456789ABCDEF", Bytes.hex(bytes)); + } } http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/16c03cda/java/kudu-client/src/test/java/org/kududb/client/TestKuduClient.java ---------------------------------------------------------------------- diff --git a/java/kudu-client/src/test/java/org/kududb/client/TestKuduClient.java b/java/kudu-client/src/test/java/org/kududb/client/TestKuduClient.java index c99238c..7ff8a18 100644 --- a/java/kudu-client/src/test/java/org/kududb/client/TestKuduClient.java +++ b/java/kudu-client/src/test/java/org/kududb/client/TestKuduClient.java @@ -20,6 +20,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.kududb.client.KuduPredicate.ComparisonOp.GREATER; +import static org.kududb.client.KuduPredicate.ComparisonOp.GREATER_EQUAL; +import static org.kududb.client.KuduPredicate.ComparisonOp.LESS; +import static org.kududb.client.KuduPredicate.ComparisonOp.LESS_EQUAL; import static org.kududb.client.RowResult.timestampToString; import java.util.ArrayList; @@ -258,6 +262,53 @@ public class TestKuduClient extends BaseKuduTest { } /** + * Test scanning with predicates. + */ + @Test + public void testScanWithPredicates() throws Exception { + Schema schema = createManyStringsSchema(); + syncClient.createTable(tableName, schema); + + KuduSession session = syncClient.newSession(); + session.setFlushMode(SessionConfiguration.FlushMode.AUTO_FLUSH_BACKGROUND); + KuduTable table = syncClient.openTable(tableName); + for (int i = 0; i < 100; i++) { + Insert insert = table.newInsert(); + PartialRow row = insert.getRow(); + row.addString("key", String.format("key_%02d", i)); + row.addString("c1", "c1_" + i); + row.addString("c2", "c2_" + i); + session.apply(insert); + } + session.flush(); + + assertEquals(100, scanTableToStrings(table).size()); + assertEquals(50, scanTableToStrings(table, + KuduPredicate.newComparisonPredicate(schema.getColumn("key"), GREATER_EQUAL, "key_50") + ).size()); + assertEquals(25, scanTableToStrings(table, + KuduPredicate.newComparisonPredicate(schema.getColumn("key"), GREATER, "key_74") + ).size()); + assertEquals(25, scanTableToStrings(table, + KuduPredicate.newComparisonPredicate(schema.getColumn("key"), GREATER, "key_24"), + KuduPredicate.newComparisonPredicate(schema.getColumn("c1"), LESS_EQUAL, "c1_49") + ).size()); + assertEquals(50, scanTableToStrings(table, + KuduPredicate.newComparisonPredicate(schema.getColumn("key"), GREATER, "key_24"), + KuduPredicate.newComparisonPredicate(schema.getColumn("key"), GREATER_EQUAL, "key_50") + ).size()); + assertEquals(0, scanTableToStrings(table, + KuduPredicate.newComparisonPredicate(schema.getColumn("c1"), GREATER, "c1_30"), + KuduPredicate.newComparisonPredicate(schema.getColumn("c2"), LESS, "c2_20") + ).size()); + assertEquals(0, scanTableToStrings(table, + // Short circuit scan + KuduPredicate.newComparisonPredicate(schema.getColumn("c2"), GREATER, "c2_30"), + KuduPredicate.newComparisonPredicate(schema.getColumn("c2"), LESS, "c2_20") + ).size()); + } + + /** * Creates a local client that we auto-close while buffering one row, then makes sure that after * closing that we can read the row. */ http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/16c03cda/java/kudu-client/src/test/java/org/kududb/client/TestKuduPredicate.java ---------------------------------------------------------------------- diff --git a/java/kudu-client/src/test/java/org/kududb/client/TestKuduPredicate.java b/java/kudu-client/src/test/java/org/kududb/client/TestKuduPredicate.java new file mode 100644 index 0000000..4915a18 --- /dev/null +++ b/java/kudu-client/src/test/java/org/kududb/client/TestKuduPredicate.java @@ -0,0 +1,628 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.kududb.client; + +import com.google.common.base.Preconditions; +import org.junit.Assert; +import org.junit.Test; +import org.kududb.ColumnSchema; +import org.kududb.Type; + +import static org.kududb.client.KuduPredicate.ComparisonOp.EQUAL; +import static org.kududb.client.KuduPredicate.ComparisonOp.GREATER; +import static org.kududb.client.KuduPredicate.ComparisonOp.GREATER_EQUAL; +import static org.kududb.client.KuduPredicate.ComparisonOp.LESS; +import static org.kududb.client.KuduPredicate.ComparisonOp.LESS_EQUAL; +import static org.kududb.client.KuduPredicate.PredicateType.RANGE; + +public class TestKuduPredicate { + + private static final ColumnSchema boolCol = + new ColumnSchema.ColumnSchemaBuilder("bool", Type.BOOL).build(); + + private static final ColumnSchema byteCol = + new ColumnSchema.ColumnSchemaBuilder("byte", Type.INT8).build(); + + private static final ColumnSchema shortCol = + new ColumnSchema.ColumnSchemaBuilder("short", Type.INT16).build(); + + private static final ColumnSchema intCol = + new ColumnSchema.ColumnSchemaBuilder("int", Type.INT32).build(); + + private static final ColumnSchema longCol = + new ColumnSchema.ColumnSchemaBuilder("long", Type.INT64).build(); + + private static final ColumnSchema floatCol = + new ColumnSchema.ColumnSchemaBuilder("float", Type.FLOAT).build(); + + private static final ColumnSchema doubleCol = + new ColumnSchema.ColumnSchemaBuilder("double", Type.DOUBLE).build(); + + private static final ColumnSchema stringCol = + new ColumnSchema.ColumnSchemaBuilder("string", Type.STRING).build(); + + private static final ColumnSchema binaryCol = + new ColumnSchema.ColumnSchemaBuilder("binary", Type.BINARY).build(); + + private static KuduPredicate intRange(int lower, int upper) { + Preconditions.checkArgument(lower < upper); + return new KuduPredicate(RANGE, intCol, Bytes.fromInt(lower), Bytes.fromInt(upper)); + } + + private void testMerge(KuduPredicate a, + KuduPredicate b, + KuduPredicate expected) { + + Assert.assertEquals(expected, a.merge(b)); + Assert.assertEquals(expected, b.merge(a)); + } + + /** + * Tests merges on all types of integer predicates. + */ + @Test + public void testMergeInt() { + + // Equality + Equality + + // | + // | + // = + // | + testMerge(KuduPredicate.newComparisonPredicate(intCol, EQUAL, 0), + KuduPredicate.newComparisonPredicate(intCol, EQUAL, 0), + KuduPredicate.newComparisonPredicate(intCol, EQUAL, 0)); + // | + // | + // = + // None + testMerge(KuduPredicate.newComparisonPredicate(intCol, EQUAL, 0), + KuduPredicate.newComparisonPredicate(intCol, EQUAL, 1), + KuduPredicate.none(intCol)); + + // Range + Equality + + // [--------> + // | + // = + // | + testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 0), + KuduPredicate.newComparisonPredicate(intCol, EQUAL, 10), + KuduPredicate.newComparisonPredicate(intCol, EQUAL, 10)); + + // [--------> + // | + // = + // None + testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 10), + KuduPredicate.newComparisonPredicate(intCol, EQUAL, 0), + KuduPredicate.none(intCol)); + + // <--------) + // | + // = + // | + testMerge(KuduPredicate.newComparisonPredicate(intCol, LESS, 10), + KuduPredicate.newComparisonPredicate(intCol, EQUAL, 5), + KuduPredicate.newComparisonPredicate(intCol, EQUAL, 5)); + + // <--------) + // | + // = + // None + testMerge(KuduPredicate.newComparisonPredicate(intCol, LESS, 0), + KuduPredicate.newComparisonPredicate(intCol, EQUAL, 10), + KuduPredicate.none(intCol)); + + // Unbounded Range + Unbounded Range + + // [--------> AND + // [--------> + // = + // [--------> + + testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 0), + KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 0), + KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 0)); + + // [--------> AND + // [-----> + // = + // [-----> + testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 0), + KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 5), + KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 5)); + + // <--------) AND + // <--------) + // = + // <--------) + + testMerge(KuduPredicate.newComparisonPredicate(intCol, LESS, 0), + KuduPredicate.newComparisonPredicate(intCol, LESS, 0), + KuduPredicate.newComparisonPredicate(intCol, LESS, 0)); + + // <--------) AND + // <----) + // = + // <----) + + testMerge(KuduPredicate.newComparisonPredicate(intCol, LESS, 0), + KuduPredicate.newComparisonPredicate(intCol, LESS, -10), + KuduPredicate.newComparisonPredicate(intCol, LESS, -10)); + + // [--------> AND + // <-------) + // = + // [----) + testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 0), + KuduPredicate.newComparisonPredicate(intCol, LESS, 10), + intRange(0, 10)); + + // [-----> AND + // <----) + // = + // | + testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 5), + KuduPredicate.newComparisonPredicate(intCol, LESS, 6), + KuduPredicate.newComparisonPredicate(intCol, EQUAL, 5)); + + // [-----> AND + // <---) + // = + // None + testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 5), + KuduPredicate.newComparisonPredicate(intCol, LESS, 5), + KuduPredicate.none(intCol)); + + // [-----> AND + // <---) + // = + // None + testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 5), + KuduPredicate.newComparisonPredicate(intCol, LESS, 3), + KuduPredicate.none(intCol)); + + // Range + Range + + // [--------) AND + // [--------) + // = + // [--------) + + testMerge(intRange(0, 10), + intRange(0, 10), + intRange(0, 10)); + + // [--------) AND + // [----) + // = + // [----) + testMerge(intRange(0, 10), + intRange(0, 5), + intRange(0, 5)); + + // [--------) AND + // [----) + // = + // [----) + testMerge(intRange(0, 10), + intRange(3, 8), + intRange(3, 8)); + + // [-----) AND + // [------) + // = + // [---) + testMerge(intRange(0, 8), + intRange(3, 10), + intRange(3, 8)); + // [--) AND + // [---) + // = + // None + testMerge(intRange(0, 5), + intRange(5, 10), + KuduPredicate.none(intCol)); + + // [--) AND + // [---) + // = + // None + testMerge(intRange(0, 3), + intRange(5, 10), + KuduPredicate.none(intCol)); + + // Lower Bound + Range + + // [------------> + // [---) + // = + // [---) + testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 0), + intRange(5, 10), + intRange(5, 10)); + + // [------------> + // [--------) + // = + // [--------) + testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 5), + intRange(5, 10), + intRange(5, 10)); + + // [------------> + // [--------) + // = + // [---) + testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 5), + intRange(0, 10), + intRange(5, 10)); + + // [-------> + // [-----) + // = + // None + testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 10), + intRange(0, 5), + KuduPredicate.none(intCol)); + + // Upper Bound + Range + + // <------------) + // [---) + // = + // [---) + testMerge(KuduPredicate.newComparisonPredicate(intCol, LESS, 10), + intRange(3, 8), + intRange(3, 8)); + + // <------------) + // [--------) + // = + // [--------) + testMerge(KuduPredicate.newComparisonPredicate(intCol, LESS, 10), + intRange(5, 10), + intRange(5, 10)); + + + // <------------) + // [--------) + // = + // [----) + testMerge(KuduPredicate.newComparisonPredicate(intCol, LESS, 5), + intRange(0, 10), + intRange(0, 5)); + + // Range + Equality + + // [---) AND + // | + // = + // None + testMerge(intRange(3, 5), + KuduPredicate.newComparisonPredicate(intCol, EQUAL, 1), + KuduPredicate.none(intCol)); + + // [---) AND + // | + // = + // | + testMerge(intRange(0, 5), + KuduPredicate.newComparisonPredicate(intCol, EQUAL, 0), + KuduPredicate.newComparisonPredicate(intCol, EQUAL, 0)); + + // [---) AND + // | + // = + // | + testMerge(intRange(0, 5), + KuduPredicate.newComparisonPredicate(intCol, EQUAL, 3), + KuduPredicate.newComparisonPredicate(intCol, EQUAL, 3)); + + // [---) AND + // | + // = + // None + testMerge(intRange(0, 5), + KuduPredicate.newComparisonPredicate(intCol, EQUAL, 5), + KuduPredicate.none(intCol)); + + // [---) AND + // | + // = + // None + testMerge(intRange(0, 5), + KuduPredicate.newComparisonPredicate(intCol, EQUAL, 7), + KuduPredicate.none(intCol)); + + // None + + // None AND + // [----> + // = + // None + testMerge(KuduPredicate.none(intCol), + KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 0), + KuduPredicate.none(intCol)); + // None AND + // <----) + // = + // None + testMerge(KuduPredicate.none(intCol), + KuduPredicate.newComparisonPredicate(intCol, LESS, 0), + KuduPredicate.none(intCol)); + + // None AND + // [----) + // = + // None + testMerge(KuduPredicate.none(intCol), + intRange(3, 7), + KuduPredicate.none(intCol)); + + // None AND + // | + // = + // None + testMerge(KuduPredicate.none(intCol), + KuduPredicate.newComparisonPredicate(intCol, EQUAL, 5), + KuduPredicate.none(intCol)); + + // None AND + // None + // = + // None + testMerge(KuduPredicate.none(intCol), + KuduPredicate.none(intCol), + KuduPredicate.none(intCol)); + } + + /** + * Tests tricky merges on a var length type. + */ + @Test + public void testMergeString() { + + // [-----> + // <-----) + // = + // None + testMerge(KuduPredicate.newComparisonPredicate(stringCol, GREATER_EQUAL, "b\0"), + KuduPredicate.newComparisonPredicate(stringCol, LESS, "b"), + KuduPredicate.none(stringCol)); + + // [-----> + // <-----) + // = + // None + testMerge(KuduPredicate.newComparisonPredicate(stringCol, GREATER_EQUAL, "b"), + KuduPredicate.newComparisonPredicate(stringCol, LESS, "b"), + KuduPredicate.none(stringCol)); + + // [-----> + // <----) + // = + // | + testMerge(KuduPredicate.newComparisonPredicate(stringCol, GREATER_EQUAL, "b"), + KuduPredicate.newComparisonPredicate(stringCol, LESS, "b\0"), + KuduPredicate.newComparisonPredicate(stringCol, EQUAL, "b")); + + // [-----> + // <-----) + // = + // [--) + testMerge(KuduPredicate.newComparisonPredicate(stringCol, GREATER_EQUAL, "a"), + KuduPredicate.newComparisonPredicate(stringCol, LESS, "a\0\0"), + new KuduPredicate(RANGE, stringCol, + Bytes.fromString("a"), Bytes.fromString("a\0\0"))); + } + + @Test + public void testBoolean() { + + // b >= false + Assert.assertEquals(KuduPredicate.newIsNotNullPredicate(boolCol), + KuduPredicate.newComparisonPredicate(boolCol, GREATER_EQUAL, false)); + // b > false + Assert.assertEquals(KuduPredicate.newComparisonPredicate(boolCol, EQUAL, true), + KuduPredicate.newComparisonPredicate(boolCol, GREATER, false)); + // b = false + Assert.assertEquals(KuduPredicate.newComparisonPredicate(boolCol, EQUAL, false), + KuduPredicate.newComparisonPredicate(boolCol, EQUAL, false)); + // b < false + Assert.assertEquals(KuduPredicate.none(boolCol), + KuduPredicate.newComparisonPredicate(boolCol, LESS, false)); + // b <= false + Assert.assertEquals(KuduPredicate.newComparisonPredicate(boolCol, EQUAL, false), + KuduPredicate.newComparisonPredicate(boolCol, LESS_EQUAL, false)); + + // b >= true + Assert.assertEquals(KuduPredicate.newComparisonPredicate(boolCol, EQUAL, true), + KuduPredicate.newComparisonPredicate(boolCol, GREATER_EQUAL, true)); + // b > true + Assert.assertEquals(KuduPredicate.none(boolCol), + KuduPredicate.newComparisonPredicate(boolCol, GREATER, true)); + // b = true + Assert.assertEquals(KuduPredicate.newComparisonPredicate(boolCol, EQUAL, true), + KuduPredicate.newComparisonPredicate(boolCol, EQUAL, true)); + // b < true + Assert.assertEquals(KuduPredicate.newComparisonPredicate(boolCol, EQUAL, false), + KuduPredicate.newComparisonPredicate(boolCol, LESS, true)); + // b <= true + Assert.assertEquals(KuduPredicate.newIsNotNullPredicate(boolCol), + KuduPredicate.newComparisonPredicate(boolCol, LESS_EQUAL, true)); + } + + /** + * Tests basic predicate merges across all types. + */ + @Test + public void testAllTypesMerge() { + + testMerge(KuduPredicate.newComparisonPredicate(boolCol, GREATER_EQUAL, false), + KuduPredicate.newComparisonPredicate(boolCol, LESS, true), + new KuduPredicate(KuduPredicate.PredicateType.EQUALITY, + boolCol, + Bytes.fromBoolean(false), + null)); + + testMerge(KuduPredicate.newComparisonPredicate(boolCol, GREATER_EQUAL, false), + KuduPredicate.newComparisonPredicate(boolCol, LESS_EQUAL, true), + KuduPredicate.newIsNotNullPredicate(boolCol)); + + testMerge(KuduPredicate.newComparisonPredicate(byteCol, GREATER_EQUAL, 0), + KuduPredicate.newComparisonPredicate(byteCol, LESS, 10), + new KuduPredicate(RANGE, + byteCol, + new byte[] { (byte) 0 }, + new byte[] { (byte) 10 })); + + testMerge(KuduPredicate.newComparisonPredicate(shortCol, GREATER_EQUAL, 0), + KuduPredicate.newComparisonPredicate(shortCol, LESS, 10), + new KuduPredicate(RANGE, + shortCol, + Bytes.fromShort((short) 0), + Bytes.fromShort((short) 10))); + + testMerge(KuduPredicate.newComparisonPredicate(longCol, GREATER_EQUAL, 0), + KuduPredicate.newComparisonPredicate(longCol, LESS, 10), + new KuduPredicate(RANGE, + longCol, + Bytes.fromLong(0), + Bytes.fromLong(10))); + + testMerge(KuduPredicate.newComparisonPredicate(floatCol, GREATER_EQUAL, 123.45f), + KuduPredicate.newComparisonPredicate(floatCol, LESS, 678.90f), + new KuduPredicate(RANGE, + floatCol, + Bytes.fromFloat(123.45f), + Bytes.fromFloat(678.90f))); + + testMerge(KuduPredicate.newComparisonPredicate(doubleCol, GREATER_EQUAL, 123.45), + KuduPredicate.newComparisonPredicate(doubleCol, LESS, 678.90), + new KuduPredicate(RANGE, + doubleCol, + Bytes.fromDouble(123.45), + Bytes.fromDouble(678.90))); + + testMerge(KuduPredicate.newComparisonPredicate(binaryCol, GREATER_EQUAL, + new byte[] { 0, 1, 2, 3, 4, 5, 6 }), + KuduPredicate.newComparisonPredicate(binaryCol, LESS, new byte[] { 10 }), + new KuduPredicate(RANGE, + binaryCol, + new byte[] { 0, 1, 2, 3, 4, 5, 6 }, + new byte[] { 10 })); + } + + @Test + public void testLessEqual() { + Assert.assertEquals(KuduPredicate.newComparisonPredicate(byteCol, LESS_EQUAL, 10), + KuduPredicate.newComparisonPredicate(byteCol, LESS, 11)); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(shortCol, LESS_EQUAL, 10), + KuduPredicate.newComparisonPredicate(shortCol, LESS, 11)); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(intCol, LESS_EQUAL, 10), + KuduPredicate.newComparisonPredicate(intCol, LESS, 11)); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(longCol, LESS_EQUAL, 10), + KuduPredicate.newComparisonPredicate(longCol, LESS, 11)); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(floatCol, LESS_EQUAL, 12.345f), + KuduPredicate.newComparisonPredicate(floatCol, LESS, Math.nextAfter(12.345f, Float.POSITIVE_INFINITY))); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(doubleCol, LESS_EQUAL, 12.345), + KuduPredicate.newComparisonPredicate(doubleCol, LESS, Math.nextAfter(12.345, Float.POSITIVE_INFINITY))); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(stringCol, LESS_EQUAL, "a"), + KuduPredicate.newComparisonPredicate(stringCol, LESS, "a\0")); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(binaryCol, LESS_EQUAL, new byte[] { (byte) 10 }), + KuduPredicate.newComparisonPredicate(binaryCol, LESS, new byte[] { (byte) 10, (byte) 0 })); + + Assert.assertEquals(KuduPredicate.newComparisonPredicate(byteCol, LESS_EQUAL, Byte.MAX_VALUE), + KuduPredicate.newIsNotNullPredicate(byteCol)); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(shortCol, LESS_EQUAL, Short.MAX_VALUE), + KuduPredicate.newIsNotNullPredicate(shortCol)); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(intCol, LESS_EQUAL, Integer.MAX_VALUE), + KuduPredicate.newIsNotNullPredicate(intCol)); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(longCol, LESS_EQUAL, Long.MAX_VALUE), + KuduPredicate.newIsNotNullPredicate(longCol)); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(floatCol, LESS_EQUAL, Float.MAX_VALUE), + KuduPredicate.newComparisonPredicate(floatCol, LESS, Float.POSITIVE_INFINITY)); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(floatCol, LESS_EQUAL, Float.POSITIVE_INFINITY), + KuduPredicate.newIsNotNullPredicate(floatCol)); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(doubleCol, LESS_EQUAL, Double.MAX_VALUE), + KuduPredicate.newComparisonPredicate(doubleCol, LESS, Double.POSITIVE_INFINITY)); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(doubleCol, LESS_EQUAL, Double.POSITIVE_INFINITY), + KuduPredicate.newIsNotNullPredicate(doubleCol)); + } + + @Test + public void testGreater() { + Assert.assertEquals(KuduPredicate.newComparisonPredicate(byteCol, GREATER_EQUAL, 11), + KuduPredicate.newComparisonPredicate(byteCol, GREATER, 10)); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(shortCol, GREATER_EQUAL, 11), + KuduPredicate.newComparisonPredicate(shortCol, GREATER, 10)); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 11), + KuduPredicate.newComparisonPredicate(intCol, GREATER, 10)); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(longCol, GREATER_EQUAL, 11), + KuduPredicate.newComparisonPredicate(longCol, GREATER, 10)); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(floatCol, GREATER_EQUAL, Math.nextAfter(12.345f, Float.MAX_VALUE)), + KuduPredicate.newComparisonPredicate(floatCol, GREATER, 12.345f)); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(doubleCol, GREATER_EQUAL, Math.nextAfter(12.345, Float.MAX_VALUE)), + KuduPredicate.newComparisonPredicate(doubleCol, GREATER, 12.345)); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(stringCol, GREATER_EQUAL, "a\0"), + KuduPredicate.newComparisonPredicate(stringCol, GREATER, "a")); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(binaryCol, GREATER_EQUAL, new byte[] { (byte) 10, (byte) 0 }), + KuduPredicate.newComparisonPredicate(binaryCol, GREATER, new byte[] { (byte) 10 })); + + Assert.assertEquals(KuduPredicate.none(byteCol), + KuduPredicate.newComparisonPredicate(byteCol, GREATER, Byte.MAX_VALUE)); + Assert.assertEquals(KuduPredicate.none(shortCol), + KuduPredicate.newComparisonPredicate(shortCol, GREATER, Short.MAX_VALUE)); + Assert.assertEquals(KuduPredicate.none(intCol), + KuduPredicate.newComparisonPredicate(intCol, GREATER, Integer.MAX_VALUE)); + Assert.assertEquals(KuduPredicate.none(longCol), + KuduPredicate.newComparisonPredicate(longCol, GREATER, Long.MAX_VALUE)); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(floatCol, GREATER_EQUAL, Float.POSITIVE_INFINITY), + KuduPredicate.newComparisonPredicate(floatCol, GREATER, Float.MAX_VALUE)); + Assert.assertEquals(KuduPredicate.none(floatCol), + KuduPredicate.newComparisonPredicate(floatCol, GREATER, Float.POSITIVE_INFINITY)); + Assert.assertEquals(KuduPredicate.newComparisonPredicate(doubleCol, GREATER_EQUAL, Double.POSITIVE_INFINITY), + KuduPredicate.newComparisonPredicate(doubleCol, GREATER, Double.MAX_VALUE)); + Assert.assertEquals(KuduPredicate.none(doubleCol), + KuduPredicate.newComparisonPredicate(doubleCol, GREATER, Double.POSITIVE_INFINITY)); + } + + @Test + public void testToString() { + Assert.assertEquals("`bool` = true", + KuduPredicate.newComparisonPredicate(boolCol, EQUAL, true).toString()); + Assert.assertEquals("`byte` = 11", + KuduPredicate.newComparisonPredicate(byteCol, EQUAL, 11).toString()); + Assert.assertEquals("`short` = 11", + KuduPredicate.newComparisonPredicate(shortCol, EQUAL, 11).toString()); + Assert.assertEquals("`int` = -123", + KuduPredicate.newComparisonPredicate(intCol, EQUAL, -123).toString()); + Assert.assertEquals("`long` = 5454", + KuduPredicate.newComparisonPredicate(longCol, EQUAL, 5454).toString()); + Assert.assertEquals("`float` = 123.456", + KuduPredicate.newComparisonPredicate(floatCol, EQUAL, 123.456f).toString()); + Assert.assertEquals("`double` = 123.456", + KuduPredicate.newComparisonPredicate(doubleCol, EQUAL, 123.456).toString()); + Assert.assertEquals("`string` = \"my string\"", + KuduPredicate.newComparisonPredicate(stringCol, EQUAL, "my string").toString()); + Assert.assertEquals("`binary` = 0xAB01CD", KuduPredicate.newComparisonPredicate( + binaryCol, EQUAL, new byte[] { (byte) 0xAB, (byte) 0x01, (byte) 0xCD }).toString()); + } +}