You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kudu.apache.org by to...@apache.org on 2017/02/01 21:25:43 UTC
[2/3] kudu git commit: KUDU-1595 and KUDU-1642: IS NOT NULL and IS
NULL predicates
KUDU-1595 and KUDU-1642: IS NOT NULL and IS NULL predicates
This patch exposes client APIs for the IS NOT NULL predicate and
adds an IS NULL predicate type.
These are both purely new features, so the changes are backwards-compatible.
Change-Id: Ide23ab6d3cad524553aa6ea5c44570fdadde5ace
Reviewed-on: http://gerrit.cloudera.org:8080/5439
Reviewed-by: Dan Burkert <da...@apache.org>
Tested-by: Kudu Jenkins
Project: http://git-wip-us.apache.org/repos/asf/kudu/repo
Commit: http://git-wip-us.apache.org/repos/asf/kudu/commit/431aee53
Tree: http://git-wip-us.apache.org/repos/asf/kudu/tree/431aee53
Diff: http://git-wip-us.apache.org/repos/asf/kudu/diff/431aee53
Branch: refs/heads/master
Commit: 431aee53fd269b5699095fb1e7fd5402644fe454
Parents: 588538d
Author: Will Berkeley <wd...@gmail.com>
Authored: Fri Dec 9 02:39:43 2016 -0500
Committer: Will Berkeley <wd...@gmail.com>
Committed: Wed Feb 1 20:42:21 2017 +0000
----------------------------------------------------------------------
.../org/apache/kudu/client/KuduPredicate.java | 81 +++++---
.../org/apache/kudu/client/TestKuduClient.java | 21 +-
.../apache/kudu/client/TestKuduPredicate.java | 204 ++++++++++++++++---
.../apache/kudu/client/TestPartitionPruner.java | 6 +-
.../apache/kudu/client/TestScanPredicate.java | 30 +++
src/kudu/cfile/binary_plain_block.cc | 1 +
src/kudu/client/client-test.cc | 193 ++++++++++++++++++
src/kudu/client/client.cc | 63 +++---
src/kudu/client/client.h | 24 +++
src/kudu/client/predicate-test.cc | 41 +++-
src/kudu/client/scan_predicate-internal.h | 44 ++++
src/kudu/client/scan_predicate.h | 2 +
src/kudu/client/scan_token-internal.cc | 4 +
src/kudu/client/scan_token-test.cc | 52 +++++
src/kudu/client/table-internal.h | 18 ++
src/kudu/common/column_predicate-test.cc | 80 +++++++-
src/kudu/common/column_predicate.cc | 100 +++++++--
src/kudu/common/column_predicate.h | 23 ++-
src/kudu/common/common.proto | 3 +
src/kudu/common/key_util.cc | 6 +-
src/kudu/common/scan_spec.cc | 1 +
src/kudu/common/wire_protocol.cc | 9 +-
22 files changed, 897 insertions(+), 109 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/java/kudu-client/src/main/java/org/apache/kudu/client/KuduPredicate.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/client/KuduPredicate.java b/java/kudu-client/src/main/java/org/apache/kudu/client/KuduPredicate.java
index ae404b4..3265d19 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/KuduPredicate.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/KuduPredicate.java
@@ -59,6 +59,8 @@ public class KuduPredicate {
RANGE,
/** A predicate which filters all null rows. */
IS_NOT_NULL,
+ /** A predicate which filters all non-null rows. */
+ IS_NULL,
/** A predicate which filters all rows not matching a list of values. */
IN_LIST,
}
@@ -120,7 +122,7 @@ public class KuduPredicate {
if (value) {
return new KuduPredicate(PredicateType.EQUALITY, column, Bytes.fromBoolean(true), null);
} else {
- return isNotNull(column);
+ return newIsNotNullPredicate(column);
}
}
case EQUAL: return new KuduPredicate(PredicateType.EQUALITY, column,
@@ -138,7 +140,7 @@ public class KuduPredicate {
// b <= true -> b IS NOT NULL
// b <= false -> b = false
if (value) {
- return isNotNull(column);
+ return newIsNotNullPredicate(column);
} else {
return new KuduPredicate(PredicateType.EQUALITY, column, Bytes.fromBoolean(false), null);
}
@@ -170,7 +172,7 @@ public class KuduPredicate {
// 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 isNotNull(column);
+ return newIsNotNullPredicate(column);
}
value += 1;
op = ComparisonOp.LESS;
@@ -207,7 +209,7 @@ public class KuduPredicate {
switch (op) {
case GREATER_EQUAL:
if (value == minValue) {
- return isNotNull(column);
+ return newIsNotNullPredicate(column);
} else if (value == maxValue) {
return new KuduPredicate(PredicateType.EQUALITY, column, bytes, null);
}
@@ -236,7 +238,7 @@ public class KuduPredicate {
checkColumn(column, Type.FLOAT);
if (op == ComparisonOp.LESS_EQUAL) {
if (value == Float.POSITIVE_INFINITY) {
- return isNotNull(column);
+ return newIsNotNullPredicate(column);
}
value = Math.nextAfter(value, Float.POSITIVE_INFINITY);
op = ComparisonOp.LESS;
@@ -252,7 +254,7 @@ public class KuduPredicate {
switch (op) {
case GREATER_EQUAL:
if (value == Float.NEGATIVE_INFINITY) {
- return isNotNull(column);
+ return newIsNotNullPredicate(column);
} else if (value == Float.POSITIVE_INFINITY) {
return new KuduPredicate(PredicateType.EQUALITY, column, bytes, null);
}
@@ -281,7 +283,7 @@ public class KuduPredicate {
checkColumn(column, Type.DOUBLE);
if (op == ComparisonOp.LESS_EQUAL) {
if (value == Double.POSITIVE_INFINITY) {
- return isNotNull(column);
+ return newIsNotNullPredicate(column);
}
value = Math.nextAfter(value, Double.POSITIVE_INFINITY);
op = ComparisonOp.LESS;
@@ -297,7 +299,7 @@ public class KuduPredicate {
switch (op) {
case GREATER_EQUAL:
if (value == Double.NEGATIVE_INFINITY) {
- return isNotNull(column);
+ return newIsNotNullPredicate(column);
} else if (value == Double.POSITIVE_INFINITY) {
return new KuduPredicate(PredicateType.EQUALITY, column, bytes, null);
}
@@ -337,7 +339,7 @@ public class KuduPredicate {
switch (op) {
case GREATER_EQUAL:
if (bytes.length == 0) {
- return isNotNull(column);
+ return newIsNotNullPredicate(column);
}
return new KuduPredicate(PredicateType.RANGE, column, bytes, null);
case EQUAL:
@@ -374,7 +376,7 @@ public class KuduPredicate {
switch (op) {
case GREATER_EQUAL:
if (value.length == 0) {
- return isNotNull(column);
+ return newIsNotNullPredicate(column);
}
return new KuduPredicate(PredicateType.RANGE, column, value, null);
case EQUAL:
@@ -466,6 +468,29 @@ public class KuduPredicate {
}
/**
+ * Creates a new {@code IS NOT NULL} predicate.
+ *
+ * @param column the column that the predicate applies to
+ * @return an {@code IS NOT NULL} predicate
+ */
+ public static KuduPredicate newIsNotNullPredicate(ColumnSchema column) {
+ return new KuduPredicate(PredicateType.IS_NOT_NULL, column, null, null);
+ }
+
+ /**
+ * Creates a new {@code IS NULL} predicate.
+ *
+ * @param column the column that the predicate applies to
+ * @return an {@code IS NULL} predicate
+ */
+ public static KuduPredicate newIsNullPredicate(ColumnSchema column) {
+ if (!column.isNullable()) {
+ return none(column);
+ }
+ return new KuduPredicate(PredicateType.IS_NULL, column, null, null);
+ }
+
+ /**
* @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,
@@ -505,16 +530,6 @@ public class KuduPredicate {
}
/**
- * 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 isNotNull(ColumnSchema column) {
- return new KuduPredicate(PredicateType.IS_NOT_NULL, column, null, null);
- }
-
- /**
* @return the type of this predicate
*/
PredicateType getType() {
@@ -532,21 +547,28 @@ public class KuduPredicate {
Preconditions.checkArgument(column.equals(other.column),
"predicates from different columns may not be merged");
+ // First, consider other.type == NONE, IS_NOT_NULL, or IS_NULL
// NONE predicates dominate.
if (other.type == PredicateType.NONE) {
return other;
}
- // NOT NULL is dominated by all other predicates.
- // Note: this will no longer be true when an IS NULL predicate type is
- // added.
+ // NOT NULL is dominated by all other predicates,
+ // except IS NULL, for which the merge is NONE.
if (other.type == PredicateType.IS_NOT_NULL) {
- return this;
+ return type == PredicateType.IS_NULL ? none(column) : this;
+ }
+
+ // NULL merged with any predicate type besides itself is NONE.
+ if (other.type == PredicateType.IS_NULL) {
+ return type == PredicateType.IS_NULL ? this : none(column);
}
+ // Now other.type == EQUALITY, RANGE, or IN_LIST.
switch (type) {
case NONE: return this;
case IS_NOT_NULL: return other;
+ case IS_NULL: return none(column);
case EQUALITY: {
if (other.type == PredicateType.EQUALITY) {
if (compare(column, lower, other.lower) != 0) {
@@ -626,7 +648,7 @@ public class KuduPredicate {
private static KuduPredicate buildInList(ColumnSchema column, Collection<byte[]> values) {
// IN (true, false) predicates can be simplified to IS NOT NULL.
if (column.getType().getDataType() == Common.DataType.BOOL && values.size() > 1) {
- return isNotNull(column);
+ return newIsNotNullPredicate(column);
}
switch (values.size()) {
@@ -695,6 +717,10 @@ public class KuduPredicate {
builder.setIsNotNull(builder.getIsNotNullBuilder());
break;
}
+ case IS_NULL: {
+ builder.setIsNull(builder.getIsNullBuilder());
+ break;
+ }
case IN_LIST: {
Common.ColumnPredicatePB.InList.Builder inListBuilder = builder.getInListBuilder();
for (byte[] value : inListValues) {
@@ -728,7 +754,9 @@ public class KuduPredicate {
range.hasUpper() ? range.getUpper().toByteArray() : null);
}
case IS_NOT_NULL:
- return isNotNull(column);
+ return newIsNotNullPredicate(column);
+ case IS_NULL:
+ return newIsNullPredicate(column);
case IN_LIST: {
Common.ColumnPredicatePB.InList inList = pb.getInList();
@@ -971,6 +999,7 @@ public class KuduPredicate {
return String.format("`%s` IN (%s)", column.getName(), Joiner.on(", ").join(strings));
}
case IS_NOT_NULL: return String.format("`%s` IS NOT NULL", column.getName());
+ case IS_NULL: return String.format("`%s` IS NULL", column.getName());
case NONE: return String.format("`%s` NONE", column.getName());
default: throw new IllegalArgumentException(String.format("unknown predicate type %s", type));
}
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduClient.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduClient.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduClient.java
index b72f8fc..7e5a07a 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduClient.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduClient.java
@@ -413,6 +413,9 @@ public class TestKuduClient extends BaseKuduTest {
row.addString("key", String.format("key_%02d", i));
row.addString("c1", "c1_" + i);
row.addString("c2", "c2_" + i);
+ if (i % 2 == 0) {
+ row.addString("c3", "c3_" + i);
+ }
session.apply(insert);
}
session.flush();
@@ -444,8 +447,20 @@ public class TestKuduClient extends BaseKuduTest {
// IS NOT NULL
assertEquals(100, scanTableToStrings(table,
- KuduPredicate.isNotNull(schema.getColumn("c2")),
- KuduPredicate.isNotNull(schema.getColumn("key"))
+ KuduPredicate.newIsNotNullPredicate(schema.getColumn("c1")),
+ KuduPredicate.newIsNotNullPredicate(schema.getColumn("key"))
+ ).size());
+ assertEquals(50, scanTableToStrings(table,
+ KuduPredicate.newIsNotNullPredicate(schema.getColumn("c3"))
+ ).size());
+
+ // IS NULL
+ assertEquals(0, scanTableToStrings(table,
+ KuduPredicate.newIsNullPredicate(schema.getColumn("c2")),
+ KuduPredicate.newIsNullPredicate(schema.getColumn("key"))
+ ).size());
+ assertEquals(50, scanTableToStrings(table,
+ KuduPredicate.newIsNullPredicate(schema.getColumn("c3"))
).size());
// IN list
@@ -460,7 +475,7 @@ public class TestKuduClient extends BaseKuduTest {
assertEquals(2, scanTableToStrings(table,
KuduPredicate.newInListPredicate(schema.getColumn("c2"),
ImmutableList.of("c2_30", "c2_1", "invalid", "c2_99")),
- KuduPredicate.isNotNull(schema.getColumn("c2")),
+ KuduPredicate.newIsNotNullPredicate(schema.getColumn("c2")),
KuduPredicate.newInListPredicate(schema.getColumn("key"),
ImmutableList.of("key_30", "key_45", "invalid", "key_99"))
).size());
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduPredicate.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduPredicate.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduPredicate.java
index 5afdd11..d192be8 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduPredicate.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduPredicate.java
@@ -58,7 +58,7 @@ public class TestKuduPredicate {
new ColumnSchema.ColumnSchemaBuilder("double", Type.DOUBLE).build();
private static final ColumnSchema stringCol =
- new ColumnSchema.ColumnSchemaBuilder("string", Type.STRING).build();
+ new ColumnSchema.ColumnSchemaBuilder("string", Type.STRING).nullable(true).build();
private static final ColumnSchema binaryCol =
new ColumnSchema.ColumnSchemaBuilder("binary", Type.BINARY).build();
@@ -95,6 +95,7 @@ public class TestKuduPredicate {
public void testMergeInt() {
// Equality + Equality
+ //--------------------
// |
// |
@@ -112,6 +113,7 @@ public class TestKuduPredicate {
KuduPredicate.none(intCol));
// Range + Equality
+ //--------------------
// [-------->
// |
@@ -146,6 +148,7 @@ public class TestKuduPredicate {
KuduPredicate.none(intCol));
// Unbounded Range + Unbounded Range
+ //--------------------
// [--------> AND
// [-------->
@@ -215,6 +218,7 @@ public class TestKuduPredicate {
KuduPredicate.none(intCol));
// Range + Range
+ //--------------------
// [--------) AND
// [--------)
@@ -265,6 +269,7 @@ public class TestKuduPredicate {
KuduPredicate.none(intCol));
// Lower Bound + Range
+ //--------------------
// [------------>
// [---)
@@ -299,6 +304,7 @@ public class TestKuduPredicate {
KuduPredicate.none(intCol));
// Upper Bound + Range
+ //--------------------
// <------------)
// [---)
@@ -326,6 +332,7 @@ public class TestKuduPredicate {
intRange(0, 5));
// Range + Equality
+ //--------------------
// [---) AND
// |
@@ -368,6 +375,7 @@ public class TestKuduPredicate {
KuduPredicate.none(intCol));
// IN list + IN list
+ //--------------------
// | | |
// | | |
@@ -382,15 +390,18 @@ public class TestKuduPredicate {
KuduPredicate.none(intCol));
// IN list + NOT NULL
+ //--------------------
+
testMerge(intInList(10),
- KuduPredicate.isNotNull(intCol),
+ KuduPredicate.newIsNotNullPredicate(intCol),
KuduPredicate.newComparisonPredicate(intCol, EQUAL, 10));
testMerge(intInList(10, -100),
- KuduPredicate.isNotNull(intCol),
+ KuduPredicate.newIsNotNullPredicate(intCol),
intInList(-100, 10));
// IN list + Equality
+ //--------------------
// | | |
// |
@@ -409,6 +420,7 @@ public class TestKuduPredicate {
KuduPredicate.none(intCol));
// IN list + Range
+ //--------------------
// | | | | |
// [---)
@@ -475,6 +487,7 @@ public class TestKuduPredicate {
KuduPredicate.none(intCol));
// None
+ //--------------------
// None AND
// [---->
@@ -483,6 +496,7 @@ public class TestKuduPredicate {
testMerge(KuduPredicate.none(intCol),
KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 0),
KuduPredicate.none(intCol));
+
// None AND
// <----)
// =
@@ -514,6 +528,141 @@ public class TestKuduPredicate {
testMerge(KuduPredicate.none(intCol),
KuduPredicate.none(intCol),
KuduPredicate.none(intCol));
+
+ // IS NOT NULL
+ //--------------------
+
+ // IS NOT NULL AND
+ // NONE
+ // =
+ // NONE
+ testMerge(KuduPredicate.newIsNotNullPredicate(intCol),
+ KuduPredicate.none(intCol),
+ KuduPredicate.none(intCol));
+
+ // IS NOT NULL AND
+ // IS NULL
+ // =
+ // NONE
+ testMerge(KuduPredicate.newIsNotNullPredicate(intCol),
+ KuduPredicate.newIsNullPredicate(intCol),
+ KuduPredicate.none(intCol));
+
+ // IS NOT NULL AND
+ // IS NOT NULL
+ // =
+ // IS NOT NULL
+ testMerge(KuduPredicate.newIsNotNullPredicate(intCol),
+ KuduPredicate.newIsNotNullPredicate(intCol),
+ KuduPredicate.newIsNotNullPredicate(intCol));
+
+ // IS NOT NULL AND
+ // |
+ // =
+ // |
+ testMerge(KuduPredicate.newIsNotNullPredicate(intCol),
+ KuduPredicate.newComparisonPredicate(intCol, EQUAL, 5),
+ KuduPredicate.newComparisonPredicate(intCol, EQUAL, 5));
+
+ // IS NOT NULL AND
+ // [------->
+ // =
+ // [------->
+ testMerge(KuduPredicate.newIsNotNullPredicate(intCol),
+ KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 5),
+ KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 5));
+
+ // IS NOT NULL AND
+ // <---------)
+ // =
+ // <---------)
+ testMerge(KuduPredicate.newIsNotNullPredicate(intCol),
+ KuduPredicate.newComparisonPredicate(intCol, LESS, 5),
+ KuduPredicate.newComparisonPredicate(intCol, LESS, 5));
+
+ // IS NOT NULL AND
+ // [-------)
+ // =
+ // [-------)
+ testMerge(KuduPredicate.newIsNotNullPredicate(intCol),
+ intRange(0, 12),
+ intRange(0, 12));
+
+
+ // IS NOT NULL AND
+ // | | |
+ // =
+ // | | |
+ testMerge(KuduPredicate.newIsNotNullPredicate(intCol),
+ intInList(0, 10, 20),
+ intInList(0, 10, 20));
+
+ // IS NULL
+ //--------------------
+
+ // IS NULL AND
+ // NONE
+ // =
+ // NONE
+ testMerge(KuduPredicate.newIsNullPredicate(intCol),
+ KuduPredicate.none(intCol),
+ KuduPredicate.none(intCol));
+
+ // IS NULL AND
+ // IS NULL
+ // =
+ // IS_NULL
+ testMerge(KuduPredicate.newIsNullPredicate(intCol),
+ KuduPredicate.newIsNullPredicate(intCol),
+ KuduPredicate.newIsNullPredicate(intCol));
+
+ // IS NULL AND
+ // IS NOT NULL
+ // =
+ // NONE
+ testMerge(KuduPredicate.newIsNullPredicate(intCol),
+ KuduPredicate.newIsNotNullPredicate(intCol),
+ KuduPredicate.none(intCol));
+
+ // IS NULL AND
+ // |
+ // =
+ // NONE
+ testMerge(KuduPredicate.newIsNullPredicate(intCol),
+ KuduPredicate.newComparisonPredicate(intCol, EQUAL, 5),
+ KuduPredicate.none(intCol));
+
+ // IS NULL AND
+ // [------->
+ // =
+ // NONE
+ testMerge(KuduPredicate.newIsNullPredicate(intCol),
+ KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 0),
+ KuduPredicate.none(intCol));
+
+ // IS NULL AND
+ // <---------)
+ // =
+ // NONE
+ testMerge(KuduPredicate.newIsNullPredicate(intCol),
+ KuduPredicate.newComparisonPredicate(intCol, LESS, 5),
+ KuduPredicate.none(intCol));
+
+ // IS NULL AND
+ // [-------)
+ // =
+ // NONE
+ testMerge(KuduPredicate.newIsNullPredicate(intCol),
+ intRange(0, 12),
+ KuduPredicate.none(intCol));
+
+ // IS NULL AND
+ // | | |
+ // =
+ // NONE
+ testMerge(KuduPredicate.newIsNullPredicate(intCol),
+ intInList(0, 10, 20),
+ KuduPredicate.none(intCol));
}
/**
@@ -558,7 +707,7 @@ public class TestKuduPredicate {
// [----->
// | | | |
// =
- // [--)
+ // | | |
testMerge(KuduPredicate.newComparisonPredicate(stringCol, GREATER_EQUAL, "a"),
stringInList("a", "c", "b", ""),
stringInList("a", "b", "c"));
@@ -566,8 +715,8 @@ public class TestKuduPredicate {
// IS NOT NULL
// | | | |
// =
- // [--)
- testMerge(KuduPredicate.isNotNull(stringCol),
+ // | | | |
+ testMerge(KuduPredicate.newIsNotNullPredicate(stringCol),
stringInList("a", "c", "b", ""),
stringInList("", "a", "b", "c"));
}
@@ -576,7 +725,7 @@ public class TestKuduPredicate {
public void testBoolean() {
// b >= false
- Assert.assertEquals(KuduPredicate.isNotNull(boolCol),
+ Assert.assertEquals(KuduPredicate.newIsNotNullPredicate(boolCol),
KuduPredicate.newComparisonPredicate(boolCol, GREATER_EQUAL, false));
// b > false
Assert.assertEquals(KuduPredicate.newComparisonPredicate(boolCol, EQUAL, true),
@@ -604,7 +753,7 @@ public class TestKuduPredicate {
Assert.assertEquals(KuduPredicate.newComparisonPredicate(boolCol, EQUAL, false),
KuduPredicate.newComparisonPredicate(boolCol, LESS, true));
// b <= true
- Assert.assertEquals(KuduPredicate.isNotNull(boolCol),
+ Assert.assertEquals(KuduPredicate.newIsNotNullPredicate(boolCol),
KuduPredicate.newComparisonPredicate(boolCol, LESS_EQUAL, true));
// b IN ()
@@ -619,7 +768,7 @@ public class TestKuduPredicate {
boolInList(false));
// b IN (false, true)
- Assert.assertEquals(KuduPredicate.isNotNull(boolCol),
+ Assert.assertEquals(KuduPredicate.newIsNotNullPredicate(boolCol),
boolInList(false, true, false, true));
}
@@ -638,7 +787,7 @@ public class TestKuduPredicate {
testMerge(KuduPredicate.newComparisonPredicate(boolCol, GREATER_EQUAL, false),
KuduPredicate.newComparisonPredicate(boolCol, LESS_EQUAL, true),
- KuduPredicate.isNotNull(boolCol));
+ KuduPredicate.newIsNotNullPredicate(boolCol));
testMerge(KuduPredicate.newComparisonPredicate(byteCol, GREATER_EQUAL, 0),
KuduPredicate.newComparisonPredicate(byteCol, LESS, 10),
@@ -728,21 +877,21 @@ public class TestKuduPredicate {
KuduPredicate.newComparisonPredicate(binaryCol, LESS, new byte[] { (byte) 10, (byte) 0 }));
Assert.assertEquals(KuduPredicate.newComparisonPredicate(byteCol, LESS_EQUAL, Byte.MAX_VALUE),
- KuduPredicate.isNotNull(byteCol));
+ KuduPredicate.newIsNotNullPredicate(byteCol));
Assert.assertEquals(KuduPredicate.newComparisonPredicate(shortCol, LESS_EQUAL, Short.MAX_VALUE),
- KuduPredicate.isNotNull(shortCol));
+ KuduPredicate.newIsNotNullPredicate(shortCol));
Assert.assertEquals(KuduPredicate.newComparisonPredicate(intCol, LESS_EQUAL, Integer.MAX_VALUE),
- KuduPredicate.isNotNull(intCol));
+ KuduPredicate.newIsNotNullPredicate(intCol));
Assert.assertEquals(KuduPredicate.newComparisonPredicate(longCol, LESS_EQUAL, Long.MAX_VALUE),
- KuduPredicate.isNotNull(longCol));
+ 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.isNotNull(floatCol));
+ 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.isNotNull(doubleCol));
+ KuduPredicate.newIsNotNullPredicate(doubleCol));
}
@Test
@@ -805,21 +954,21 @@ public class TestKuduPredicate {
@Test
public void testGreaterEqual() {
Assert.assertEquals(KuduPredicate.newComparisonPredicate(byteCol, GREATER_EQUAL, Byte.MIN_VALUE),
- KuduPredicate.isNotNull(byteCol));
+ KuduPredicate.newIsNotNullPredicate(byteCol));
Assert.assertEquals(KuduPredicate.newComparisonPredicate(shortCol, GREATER_EQUAL, Short.MIN_VALUE),
- KuduPredicate.isNotNull(shortCol));
+ KuduPredicate.newIsNotNullPredicate(shortCol));
Assert.assertEquals(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, Integer.MIN_VALUE),
- KuduPredicate.isNotNull(intCol));
+ KuduPredicate.newIsNotNullPredicate(intCol));
Assert.assertEquals(KuduPredicate.newComparisonPredicate(longCol, GREATER_EQUAL, Long.MIN_VALUE),
- KuduPredicate.isNotNull(longCol));
+ KuduPredicate.newIsNotNullPredicate(longCol));
Assert.assertEquals(KuduPredicate.newComparisonPredicate(floatCol, GREATER_EQUAL, Float.NEGATIVE_INFINITY),
- KuduPredicate.isNotNull(floatCol));
+ KuduPredicate.newIsNotNullPredicate(floatCol));
Assert.assertEquals(KuduPredicate.newComparisonPredicate(doubleCol, GREATER_EQUAL, Double.NEGATIVE_INFINITY),
- KuduPredicate.isNotNull(doubleCol));
+ KuduPredicate.newIsNotNullPredicate(doubleCol));
Assert.assertEquals(KuduPredicate.newComparisonPredicate(stringCol, GREATER_EQUAL, ""),
- KuduPredicate.isNotNull(stringCol));
+ KuduPredicate.newIsNotNullPredicate(stringCol));
Assert.assertEquals(KuduPredicate.newComparisonPredicate(binaryCol, GREATER_EQUAL, new byte[] {}),
- KuduPredicate.isNotNull(binaryCol));
+ KuduPredicate.newIsNotNullPredicate(binaryCol));
Assert.assertEquals(KuduPredicate.newComparisonPredicate(byteCol, GREATER_EQUAL, Byte.MAX_VALUE),
KuduPredicate.newComparisonPredicate(byteCol, EQUAL, Byte.MAX_VALUE));
@@ -858,7 +1007,12 @@ public class TestKuduPredicate {
Assert.assertEquals("`int` IN (-10, 0, 10)",
intInList(10, 0, -10).toString());
Assert.assertEquals("`string` IS NOT NULL",
- KuduPredicate.isNotNull(stringCol).toString());
+ KuduPredicate.newIsNotNullPredicate(stringCol).toString());
+ Assert.assertEquals("`string` IS NULL",
+ KuduPredicate.newIsNullPredicate(stringCol).toString());
+ // IS NULL predicate on non-nullable column = NONE predicate
+ Assert.assertEquals("`int` NONE",
+ KuduPredicate.newIsNullPredicate(intCol).toString());
Assert.assertEquals("`bool` = true", KuduPredicate.newInListPredicate(
boolCol, ImmutableList.of(true)).toString());
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/java/kudu-client/src/test/java/org/apache/kudu/client/TestPartitionPruner.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestPartitionPruner.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestPartitionPruner.java
index 2408adb..7b9c51e 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/client/TestPartitionPruner.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestPartitionPruner.java
@@ -629,6 +629,10 @@ public class TestPartitionPruner extends BaseKuduTest {
// timestamp IS NOT NULL
assertEquals(4, countPartitions(table, partitions,
- KuduPredicate.isNotNull(timestamp)));
+ KuduPredicate.newIsNotNullPredicate(timestamp)));
+
+ // timestamp IS NULL
+ assertEquals(0, countPartitions(table, partitions,
+ KuduPredicate.newIsNullPredicate(timestamp)));
}
}
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/java/kudu-client/src/test/java/org/apache/kudu/client/TestScanPredicate.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestScanPredicate.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestScanPredicate.java
index 3af4dfe..d89800c 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/client/TestScanPredicate.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestScanPredicate.java
@@ -212,6 +212,12 @@ public class TestScanPredicate extends BaseKuduTest {
KuduPredicate.newComparisonPredicate(col, ComparisonOp.LESS, v);
Assert.assertEquals(values.headSet(v).size(), countRows(table, less));
}
+
+ KuduPredicate isNotNull = KuduPredicate.newIsNotNullPredicate(col);
+ Assert.assertEquals(values.size(), countRows(table, isNotNull));
+
+ KuduPredicate isNull = KuduPredicate.newIsNullPredicate(col);
+ Assert.assertEquals(1, countRows(table, isNull));
}
@Test
@@ -446,6 +452,12 @@ public class TestScanPredicate extends BaseKuduTest {
KuduPredicate.newComparisonPredicate(col, ComparisonOp.LESS, v);
Assert.assertEquals(values.headSet(v).size(), countRows(table, less));
}
+
+ KuduPredicate isNotNull = KuduPredicate.newIsNotNullPredicate(col);
+ Assert.assertEquals(values.size(), countRows(table, isNotNull));
+
+ KuduPredicate isNull = KuduPredicate.newIsNullPredicate(col);
+ Assert.assertEquals(1, countRows(table, isNull));
}
@Test
@@ -499,6 +511,12 @@ public class TestScanPredicate extends BaseKuduTest {
KuduPredicate.newComparisonPredicate(col, ComparisonOp.LESS, v);
Assert.assertEquals(values.headSet(v).size(), countRows(table, less));
}
+
+ KuduPredicate isNotNull = KuduPredicate.newIsNotNullPredicate(col);
+ Assert.assertEquals(values.size(), countRows(table, isNotNull));
+
+ KuduPredicate isNull = KuduPredicate.newIsNullPredicate(col);
+ Assert.assertEquals(1, countRows(table, isNull));
}
@Test
@@ -552,6 +570,12 @@ public class TestScanPredicate extends BaseKuduTest {
KuduPredicate.newComparisonPredicate(col, ComparisonOp.LESS, v);
Assert.assertEquals(values.headSet(v).size(), countRows(table, less));
}
+
+ KuduPredicate isNotNull = KuduPredicate.newIsNotNullPredicate(col);
+ Assert.assertEquals(values.size(), countRows(table, isNotNull));
+
+ KuduPredicate isNull = KuduPredicate.newIsNullPredicate(col);
+ Assert.assertEquals(1, countRows(table, isNull));
}
@Test
@@ -606,5 +630,11 @@ public class TestScanPredicate extends BaseKuduTest {
KuduPredicate.newComparisonPredicate(col, ComparisonOp.LESS, v);
Assert.assertEquals(values.headSet(s).size(), countRows(table, less));
}
+
+ KuduPredicate isNotNull = KuduPredicate.newIsNotNullPredicate(col);
+ Assert.assertEquals(values.size(), countRows(table, isNotNull));
+
+ KuduPredicate isNull = KuduPredicate.newIsNullPredicate(col);
+ Assert.assertEquals(1, countRows(table, isNull));
}
}
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/src/kudu/cfile/binary_plain_block.cc
----------------------------------------------------------------------
diff --git a/src/kudu/cfile/binary_plain_block.cc b/src/kudu/cfile/binary_plain_block.cc
index ecc565f..a9eea75 100644
--- a/src/kudu/cfile/binary_plain_block.cc
+++ b/src/kudu/cfile/binary_plain_block.cc
@@ -300,6 +300,7 @@ Status BinaryPlainBlockDecoder::CopyNextValues(size_t* n, ColumnDataView* dst) {
CHECK(out_arena->RelocateSlice(elem, out));
});
}
+
Status BinaryPlainBlockDecoder::CopyNextAndEval(size_t* n,
ColumnMaterializationContext* ctx,
SelectionVectorView* sel,
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/src/kudu/client/client-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/client/client-test.cc b/src/kudu/client/client-test.cc
index 95b8b97..e6e88a1 100644
--- a/src/kudu/client/client-test.cc
+++ b/src/kudu/client/client-test.cc
@@ -4119,6 +4119,199 @@ TEST_F(ClientTest, TestReadAtSnapshotNoTimestampSet) {
EXPECT_EQ(kTabletsNum * kRowsPerTablet, total_row_count);
}
+enum IntEncoding {
+ kPlain,
+ kBitShuffle,
+ kRunLength
+};
+
+class IntEncodingNullPredicatesTest : public ClientTest,
+ public ::testing::WithParamInterface<IntEncoding> {
+};
+
+TEST_P(IntEncodingNullPredicatesTest, TestIntEncodings) {
+ // Create table with appropriate encoded, nullable column.
+ KuduSchema schema;
+ KuduSchemaBuilder b;
+ b.AddColumn("key")->Type(KuduColumnSchema::INT32)->NotNull()->PrimaryKey();
+ auto int_col = b.AddColumn("int_val")->Type(KuduColumnSchema::INT32);
+ IntEncoding enc = GetParam();
+ switch (enc) {
+ case kPlain:
+ int_col->Encoding(KuduColumnStorageAttributes::PLAIN_ENCODING);
+ break;
+ case kBitShuffle:
+ int_col->Encoding(KuduColumnStorageAttributes::BIT_SHUFFLE);
+ break;
+ case kRunLength:
+ int_col->Encoding(KuduColumnStorageAttributes::RLE);
+ break;
+ }
+ ASSERT_OK(b.Build(&schema));
+
+ string table_name = "IntEncodingNullPredicatesTestTable";
+ unique_ptr<KuduTableCreator> table_creator(client_->NewTableCreator());
+ ASSERT_OK(table_creator->table_name(table_name)
+ .schema(&schema)
+ .num_replicas(1)
+ .set_range_partition_columns({ "key" })
+ .Create());
+
+ shared_ptr<KuduTable> table;
+ ASSERT_OK(client_->OpenTable(table_name, &table));
+
+ // Insert rows.
+ shared_ptr<KuduSession> session = table->client()->NewSession();
+ ASSERT_OK(session->SetFlushMode(KuduSession::MANUAL_FLUSH));
+ session->SetTimeoutMillis(5000);
+ const size_t kNumRows = AllowSlowTests() ? 10000 : 10000;
+ for (int i = 0; i < kNumRows; i++) {
+ KuduInsert* insert = table->NewInsert();
+ KuduPartialRow* row = insert->mutable_row();
+ ASSERT_OK(row->SetInt32("key", i));
+ if (i % 2 == 0) {
+ ASSERT_OK(row->SetInt32("int_val", i));
+ } else {
+ ASSERT_OK(row->SetNull("int_val"));
+ }
+ ASSERT_OK(session->Apply(insert));
+ if (i % 10001 == 0) {
+ ASSERT_OK(session->Flush());
+ }
+ }
+ ASSERT_OK(session->Flush());
+
+ // Scan rows and check for correct counts.
+ { // IS NULL
+ KuduScanner scanner(table.get());
+ KuduPredicate *p = table->NewIsNullPredicate("int_val");
+ ASSERT_OK(scanner.AddConjunctPredicate(p));
+ ASSERT_OK(scanner.Open());
+ int count = 0;
+ KuduScanBatch batch;
+ while (scanner.HasMoreRows()) {
+ CHECK_OK(scanner.NextBatch(&batch));
+ count += batch.NumRows();
+ }
+ ASSERT_EQ(kNumRows / 2, count);
+ }
+
+ { // IS NOT NULL
+ KuduScanner scanner(table.get());
+ KuduPredicate *p = table->NewIsNotNullPredicate("int_val");
+ ASSERT_OK(scanner.AddConjunctPredicate(p));
+ ASSERT_OK(scanner.Open());
+ int count = 0;
+ KuduScanBatch batch;
+ while (scanner.HasMoreRows()) {
+ CHECK_OK(scanner.NextBatch(&batch));
+ count += batch.NumRows();
+ }
+ ASSERT_EQ(kNumRows / 2, count);
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(IntColEncodings,
+ IntEncodingNullPredicatesTest,
+ ::testing::Values(kPlain, kBitShuffle, kRunLength));
+
+
+enum BinaryEncoding {
+ kPlainBin,
+ kPrefix,
+ kDictionary
+};
+
+class BinaryEncodingNullPredicatesTest : public ClientTest,
+ public ::testing::WithParamInterface<IntEncoding> {
+};
+
+TEST_P(BinaryEncodingNullPredicatesTest, TestBinaryEncodings) {
+ // Create table with appropriate encoded, nullable column.
+ KuduSchema schema;
+ KuduSchemaBuilder b;
+ b.AddColumn("key")->Type(KuduColumnSchema::INT32)->NotNull()->PrimaryKey();
+ auto string_col = b.AddColumn("string_val")->Type(KuduColumnSchema::STRING);
+ IntEncoding enc = GetParam();
+ switch (enc) {
+ case kPlainBin:
+ string_col->Encoding(KuduColumnStorageAttributes::PLAIN_ENCODING);
+ break;
+ case kPrefix:
+ string_col->Encoding(KuduColumnStorageAttributes::PREFIX_ENCODING);
+ break;
+ case kDictionary:
+ string_col->Encoding(KuduColumnStorageAttributes::DICT_ENCODING);
+ break;
+ }
+ ASSERT_OK(b.Build(&schema));
+
+ string table_name = "BinaryEncodingNullPredicatesTestTable";
+ unique_ptr<KuduTableCreator> table_creator(client_->NewTableCreator());
+ ASSERT_OK(table_creator->table_name(table_name)
+ .schema(&schema)
+ .num_replicas(1)
+ .set_range_partition_columns({ "key" })
+ .Create());
+
+ shared_ptr<KuduTable> table;
+ ASSERT_OK(client_->OpenTable(table_name, &table));
+
+ // Insert rows.
+ shared_ptr<KuduSession> session = table->client()->NewSession();
+ ASSERT_OK(session->SetFlushMode(KuduSession::MANUAL_FLUSH));
+ session->SetTimeoutMillis(5000);
+ const size_t kNumRows = AllowSlowTests() ? 10000 : 1000;
+ for (int i = 0; i < kNumRows; i++) {
+ KuduInsert* insert = table->NewInsert();
+ KuduPartialRow* row = insert->mutable_row();
+ ASSERT_OK(row->SetInt32("key", i));
+ if (i % 2 == 0) {
+ ASSERT_OK(row->SetString("string_val", Substitute("taco %d", i % 25)));
+ } else {
+ ASSERT_OK(row->SetNull("string_val"));
+ }
+ ASSERT_OK(session->Apply(insert));
+ if (i % 10001 == 0) {
+ ASSERT_OK(session->Flush());
+ }
+ }
+ ASSERT_OK(session->Flush());
+
+ // Scan rows and check for correct counts.
+ { // IS NULL
+ KuduScanner scanner(table.get());
+ KuduPredicate *p = table->NewIsNullPredicate("string_val");
+ ASSERT_OK(scanner.AddConjunctPredicate(p));
+ ASSERT_OK(scanner.Open());
+ int count = 0;
+ KuduScanBatch batch;
+ while (scanner.HasMoreRows()) {
+ CHECK_OK(scanner.NextBatch(&batch));
+ count += batch.NumRows();
+ }
+ ASSERT_EQ(kNumRows / 2, count);
+ }
+
+ { // IS NOT NULL
+ KuduScanner scanner(table.get());
+ KuduPredicate *p = table->NewIsNotNullPredicate("string_val");
+ ASSERT_OK(scanner.AddConjunctPredicate(p));
+ ASSERT_OK(scanner.Open());
+ int count = 0;
+ KuduScanBatch batch;
+ while (scanner.HasMoreRows()) {
+ CHECK_OK(scanner.NextBatch(&batch));
+ count += batch.NumRows();
+ }
+ ASSERT_EQ(kNumRows / 2, count);
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(BinaryColEncodings,
+ BinaryEncodingNullPredicatesTest,
+ ::testing::Values(kPlainBin, kPrefix, kDictionary));
+
TEST_F(ClientTest, TestClonePredicates) {
ASSERT_NO_FATAL_FAILURE(InsertTestRows(client_table_.get(),
2, 0));
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/src/kudu/client/client.cc
----------------------------------------------------------------------
diff --git a/src/kudu/client/client.cc b/src/kudu/client/client.cc
index c6ef71f..d060852 100644
--- a/src/kudu/client/client.cc
+++ b/src/kudu/client/client.cc
@@ -64,6 +64,7 @@
#include "kudu/util/logging.h"
#include "kudu/util/net/dns_resolver.h"
#include "kudu/util/oid_generator.h"
+#include "kudu/util/scoped_cleanup.h"
#include "kudu/util/version_info.h"
using kudu::master::AlterTableRequestPB;
@@ -712,38 +713,44 @@ const PartitionSchema& KuduTable::partition_schema() const {
KuduPredicate* KuduTable::NewComparisonPredicate(const Slice& col_name,
KuduPredicate::ComparisonOp op,
KuduValue* value) {
- StringPiece name_sp(reinterpret_cast<const char*>(col_name.data()), col_name.size());
- const Schema* s = data_->schema_.schema_;
- int col_idx = s->find_column(name_sp);
- if (col_idx == Schema::kColumnNotFound) {
- // Since this function doesn't return an error, instead we create a special
- // predicate that just returns the errors when we add it to the scanner.
- //
- // This makes the API more "fluent".
- delete value; // we always take ownership of 'value'.
- return new KuduPredicate(new ErrorPredicateData(
- Status::NotFound("column not found", col_name)));
- }
-
- return new KuduPredicate(new ComparisonPredicateData(s->column(col_idx), op, value));
+ // We always take ownership of value; this ensures cleanup if the predicate is invalid.
+ auto cleanup = MakeScopedCleanup([&]() {
+ delete value;
+ });
+ const Schema& schema = *data_->schema_.schema_;
+ return data_->MakePredicate(col_name, schema, [&](const ColumnSchema& col_schema) {
+ // Ownership of value is passed to the valid returned predicate.
+ cleanup.cancel();
+ return new KuduPredicate(new ComparisonPredicateData(col_schema, op, value));
+ });
}
KuduPredicate* KuduTable::NewInListPredicate(const Slice& col_name,
vector<KuduValue*>* values) {
- StringPiece name_sp(reinterpret_cast<const char*>(col_name.data()), col_name.size());
- const Schema* s = data_->schema_.schema_;
- int col_idx = s->find_column(name_sp);
- if (col_idx == Schema::kColumnNotFound) {
- // Since this function doesn't return an error, instead we create a special
- // predicate that just returns the errors when we add it to the scanner.
- //
- // This makes the API more "fluent".
- STLDeleteElements(values); // we always take ownership of 'values'.
- delete values;
- return new KuduPredicate(new ErrorPredicateData(
- Status::NotFound("column not found", col_name)));
- }
- return new KuduPredicate(new InListPredicateData(s->column(col_idx), values));
+ // We always take ownership of values; this ensures cleanup if the predicate is invalid.
+ auto cleanup = MakeScopedCleanup([&]() {
+ STLDeleteElements(values);
+ });
+ const Schema& schema = *data_->schema_.schema_;
+ return data_->MakePredicate(col_name, schema, [&](const ColumnSchema& col_schema) {
+ // Ownership of values is passed to the valid returned predicate.
+ cleanup.cancel();
+ return new KuduPredicate(new InListPredicateData(col_schema, values));
+ });
+}
+
+KuduPredicate* KuduTable::NewIsNotNullPredicate(const Slice& col_name) {
+ const Schema& schema = *data_->schema_.schema_;
+ return data_->MakePredicate(col_name, schema, [&](const ColumnSchema& col_schema) {
+ return new KuduPredicate(new IsNotNullPredicateData(col_schema));
+ });
+}
+
+KuduPredicate* KuduTable::NewIsNullPredicate(const Slice& col_name) {
+ const Schema& schema = *data_->schema_.schema_;
+ return data_->MakePredicate(col_name, schema, [&](const ColumnSchema& col_schema) {
+ return new KuduPredicate(new IsNullPredicateData(col_schema));
+ });
}
////////////////////////////////////////////////////////////
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/src/kudu/client/client.h
----------------------------------------------------------------------
diff --git a/src/kudu/client/client.h b/src/kudu/client/client.h
index 3255b0c..16cea6a 100644
--- a/src/kudu/client/client.h
+++ b/src/kudu/client/client.h
@@ -894,6 +894,30 @@ class KUDU_EXPORT KuduTable : public sp::enable_shared_from_this<KuduTable> {
KuduPredicate* NewInListPredicate(const Slice& col_name,
std::vector<KuduValue*>* values);
+ /// Create a new IS NOT NULL predicate which can be used for scanners on this
+ /// table.
+ ///
+ /// @param [in] col_name
+ /// Name of the column to which the predicate applies
+ /// @return Raw pointer to an IS NOT NULL predicate. The caller owns the
+ /// predicate until it is passed into KuduScanner::AddConjunctPredicate().
+ /// In the case of an error (e.g. an invalid column name), a non-NULL
+ /// value is still returned. The error will be returned when attempting
+ /// to add this predicate to a KuduScanner.
+ KuduPredicate* NewIsNotNullPredicate(const Slice& col_name);
+
+ /// Create a new IS NULL predicate which can be used for scanners on this
+ /// table.
+ ///
+ /// @param [in] col_name
+ /// Name of the column to which the predicate applies
+ /// @return Raw pointer to an IS NULL predicate. The caller owns the
+ /// predicate until it is passed into KuduScanner::AddConjunctPredicate().
+ /// In the case of an error (e.g. an invalid column name), a non-NULL
+ /// value is still returned. The error will be returned when attempting
+ /// to add this predicate to a KuduScanner.
+ KuduPredicate* NewIsNullPredicate(const Slice& col_name);
+
/// @return The KuduClient object associated with the table. The caller
/// should not free the returned pointer.
KuduClient* client() const;
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/src/kudu/client/predicate-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/client/predicate-test.cc b/src/kudu/client/predicate-test.cc
index 19c408d..76bf4c4 100644
--- a/src/kudu/client/predicate-test.cc
+++ b/src/kudu/client/predicate-test.cc
@@ -165,7 +165,7 @@ class PredicateTest : public KuduTest {
// Returns a vector of floating point numbers from -50.50 (inclusive) to 49.49
// (exclusive) (100 values), plus min, max, two normals around 0, two
- // subnormals around 0, positive and negatic infinity, and NaN.
+ // subnormals around 0, positive and negative infinity, and NaN.
template <typename T>
vector<T> CreateFloatingPointValues() {
vector<T> values;
@@ -231,9 +231,9 @@ class PredicateTest : public KuduTest {
}
// Check integer predicates against the specified table. The table must have
- // key/value rows with values from CreateIntValues, plus a null value.
+ // key/value rows with values from CreateIntValues, plus one null value.
template <typename T>
- void CheckIntPredicates(shared_ptr<KuduTable> table) {
+ void CheckIntPredicates(const shared_ptr<KuduTable>& table) {
vector<T> values = CreateIntValues<T>();
vector<T> test_values = CreateIntTestValues<T>();
ASSERT_EQ(values.size() + 1, CountRows(table, {}));
@@ -333,6 +333,13 @@ class PredicateTest : public KuduTest {
int count = CountMatchedRows<T>(values, vector<T>(test_values.begin(), end));
ASSERT_EQ(count, CountRows(table, { table->NewInListPredicate("value", &vals) }));
}
+
+ // IS NOT NULL predicate
+ ASSERT_EQ(CountRows(table, {}) - 1,
+ CountRows(table, { table->NewIsNotNullPredicate("value") }));
+
+ // IS NULL predicate
+ ASSERT_EQ(1, CountRows(table, { table->NewIsNullPredicate("value") }));
}
// Check string predicates against the specified table.
@@ -442,6 +449,13 @@ class PredicateTest : public KuduTest {
int count = CountMatchedRows<string>(values, vector<string>(test_values.begin(), end));
ASSERT_EQ(count, CountRows(table, { table->NewInListPredicate("value", &vals) }));
}
+
+ // IS NOT NULL predicate
+ ASSERT_EQ(CountRows(table, {}) - 1,
+ CountRows(table, { table->NewIsNotNullPredicate("value") }));
+
+ // IS NULL predicate
+ ASSERT_EQ(1, CountRows(table, { table->NewIsNullPredicate("value") }));
}
shared_ptr<KuduClient> client_;
@@ -561,6 +575,13 @@ TEST_F(PredicateTest, TestBoolPredicates) {
KuduPredicate* pred = table->NewInListPredicate("value", &values);
ASSERT_EQ(2, CountRows(table, { pred }));
}
+
+ // IS NOT NULL predicate
+ ASSERT_EQ(CountRows(table, {}) - 1,
+ CountRows(table, { table->NewIsNotNullPredicate("value") }));
+
+ // IS NULL predicate
+ ASSERT_EQ(1, CountRows(table, { table->NewIsNullPredicate("value") }));
}
TEST_F(PredicateTest, TestInt8Predicates) {
@@ -778,6 +799,13 @@ TEST_F(PredicateTest, TestFloatPredicates) {
int count = CountMatchedRows<float>(values, vector<float>(test_values.begin(), end));
ASSERT_EQ(count, CountRows(table, { table->NewInListPredicate("value", &vals) }));
}
+
+ // IS NOT NULL predicate
+ ASSERT_EQ(values.size(),
+ CountRows(table, { table->NewIsNotNullPredicate("value") }));
+
+ // IS NULL predicate
+ ASSERT_EQ(1, CountRows(table, { table->NewIsNullPredicate("value") }));
}
TEST_F(PredicateTest, TestDoublePredicates) {
@@ -895,6 +923,13 @@ TEST_F(PredicateTest, TestDoublePredicates) {
int count = CountMatchedRows<double>(values, vector<double>(test_values.begin(), end));
ASSERT_EQ(count, CountRows(table, { table->NewInListPredicate("value", &vals) }));
}
+
+ // IS NOT NULL predicate
+ ASSERT_EQ(values.size(),
+ CountRows(table, { table->NewIsNotNullPredicate("value") }));
+
+ // IS NULL predicate
+ ASSERT_EQ(1, CountRows(table, { table->NewIsNullPredicate("value") }));
}
TEST_F(PredicateTest, TestStringPredicates) {
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/src/kudu/client/scan_predicate-internal.h
----------------------------------------------------------------------
diff --git a/src/kudu/client/scan_predicate-internal.h b/src/kudu/client/scan_predicate-internal.h
index e0757ae..c3716cc 100644
--- a/src/kudu/client/scan_predicate-internal.h
+++ b/src/kudu/client/scan_predicate-internal.h
@@ -116,6 +116,50 @@ class InListPredicateData : public KuduPredicate::Data {
std::vector<KuduValue*> vals_;
};
+// A predicate for selecting non-null values.
+class IsNotNullPredicateData : public KuduPredicate::Data {
+ public:
+ explicit IsNotNullPredicateData(ColumnSchema col)
+ : col_(std::move(col)) {
+ }
+
+ Status AddToScanSpec(ScanSpec* spec, Arena* /*arena*/) override {
+ spec->AddPredicate(ColumnPredicate::IsNotNull(col_));
+ return Status::OK();
+ }
+
+ IsNotNullPredicateData* Clone() const override {
+ return new IsNotNullPredicateData(col_);
+ }
+
+ private:
+ friend class KuduScanner;
+
+ ColumnSchema col_;
+};
+
+// A predicate for selecting null values.
+class IsNullPredicateData : public KuduPredicate::Data {
+ public:
+ explicit IsNullPredicateData(ColumnSchema col)
+ : col_(std::move(col)) {
+ }
+
+ Status AddToScanSpec(ScanSpec* spec, Arena* /*arena*/) override {
+ spec->AddPredicate(ColumnPredicate::IsNull(col_));
+ return Status::OK();
+ }
+
+ IsNullPredicateData* Clone() const override {
+ return new IsNullPredicateData(col_);
+ }
+
+ private:
+ friend class KuduScanner;
+
+ ColumnSchema col_;
+};
+
} // namespace client
} // namespace kudu
#endif /* KUDU_CLIENT_SCAN_PREDICATE_INTERNAL_H */
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/src/kudu/client/scan_predicate.h
----------------------------------------------------------------------
diff --git a/src/kudu/client/scan_predicate.h b/src/kudu/client/scan_predicate.h
index bb97151..16fd118 100644
--- a/src/kudu/client/scan_predicate.h
+++ b/src/kudu/client/scan_predicate.h
@@ -58,6 +58,8 @@ class KUDU_EXPORT KuduPredicate {
friend class ComparisonPredicateData;
friend class ErrorPredicateData;
friend class InListPredicateData;
+ friend class IsNotNullPredicateData;
+ friend class IsNullPredicateData;
friend class KuduTable;
friend class ScanConfiguration;
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/src/kudu/client/scan_token-internal.cc
----------------------------------------------------------------------
diff --git a/src/kudu/client/scan_token-internal.cc b/src/kudu/client/scan_token-internal.cc
index 678b915..31b22f5 100644
--- a/src/kudu/client/scan_token-internal.cc
+++ b/src/kudu/client/scan_token-internal.cc
@@ -170,6 +170,10 @@ Status KuduScanTokenBuilder::Data::Build(vector<KuduScanToken*>* tokens) {
KuduClient* client = table->client();
configuration_.OptimizeScanSpec();
+ if (configuration_.spec().CanShortCircuit()) {
+ return Status::OK();
+ }
+
ScanTokenPB pb;
pb.set_table_name(table->name());
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/src/kudu/client/scan_token-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/client/scan_token-test.cc b/src/kudu/client/scan_token-test.cc
index 8875315..42028e3 100644
--- a/src/kudu/client/scan_token-test.cc
+++ b/src/kudu/client/scan_token-test.cc
@@ -195,6 +195,32 @@ TEST_F(ScanTokenTest, TestScanTokens) {
NO_FATALS(VerifyTabletInfo(tokens));
}
+ { // IS NOT NULL predicate
+ vector<KuduScanToken*> tokens;
+ ElementDeleter deleter(&tokens);
+ KuduScanTokenBuilder builder(table.get());
+ unique_ptr<KuduPredicate> predicate(table->NewIsNotNullPredicate("col"));
+ ASSERT_OK(builder.AddConjunctPredicate(predicate.release()));
+ ASSERT_OK(builder.Build(&tokens));
+
+ ASSERT_GE(8, tokens.size());
+ ASSERT_EQ(200, CountRows(tokens));
+ NO_FATALS(VerifyTabletInfo(tokens));
+ }
+
+ { // IS NULL predicate
+ vector<KuduScanToken*> tokens;
+ ElementDeleter deleter(&tokens);
+ KuduScanTokenBuilder builder(table.get());
+ unique_ptr<KuduPredicate> predicate(table->NewIsNullPredicate("col"));
+ ASSERT_OK(builder.AddConjunctPredicate(predicate.release()));
+ ASSERT_OK(builder.Build(&tokens));
+
+ ASSERT_GE(0, tokens.size());
+ ASSERT_EQ(0, CountRows(tokens));
+ NO_FATALS(VerifyTabletInfo(tokens));
+ }
+
{ // primary key bound
vector<KuduScanToken*> tokens;
ElementDeleter deleter(&tokens);
@@ -307,6 +333,32 @@ TEST_F(ScanTokenTest, TestScanTokensWithNonCoveringRange) {
NO_FATALS(VerifyTabletInfo(tokens));
}
+ { // IS NOT NULL predicate
+ vector<KuduScanToken*> tokens;
+ ElementDeleter deleter(&tokens);
+ KuduScanTokenBuilder builder(table.get());
+ unique_ptr<KuduPredicate> predicate(table->NewIsNotNullPredicate("col"));
+ ASSERT_OK(builder.AddConjunctPredicate(predicate.release()));
+ ASSERT_OK(builder.Build(&tokens));
+
+ ASSERT_EQ(6, tokens.size());
+ ASSERT_EQ(300, CountRows(tokens));
+ NO_FATALS(VerifyTabletInfo(tokens));
+ }
+
+ { // IS NULL predicate
+ vector<KuduScanToken*> tokens;
+ ElementDeleter deleter(&tokens);
+ KuduScanTokenBuilder builder(table.get());
+ unique_ptr<KuduPredicate> predicate(table->NewIsNullPredicate("col"));
+ ASSERT_OK(builder.AddConjunctPredicate(predicate.release()));
+ ASSERT_OK(builder.Build(&tokens));
+
+ ASSERT_GE(0, tokens.size());
+ ASSERT_EQ(0, CountRows(tokens));
+ NO_FATALS(VerifyTabletInfo(tokens));
+ }
+
{ // primary key bound
vector<KuduScanToken*> tokens;
ElementDeleter deleter(&tokens);
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/src/kudu/client/table-internal.h
----------------------------------------------------------------------
diff --git a/src/kudu/client/table-internal.h b/src/kudu/client/table-internal.h
index a29c146..748a220 100644
--- a/src/kudu/client/table-internal.h
+++ b/src/kudu/client/table-internal.h
@@ -21,6 +21,7 @@
#include "kudu/common/partition.h"
#include "kudu/client/client.h"
+#include "kudu/client/scan_predicate-internal.h"
namespace kudu {
@@ -36,6 +37,23 @@ class KuduTable::Data {
PartitionSchema partition_schema);
~Data();
+ template<class PredicateMakerFunc>
+ static KuduPredicate* MakePredicate(const Slice& col_name,
+ const Schema& schema,
+ const PredicateMakerFunc& f) {
+ StringPiece name_sp(reinterpret_cast<const char*>(col_name.data()), col_name.size());
+ int col_idx = schema.find_column(name_sp);
+ if (col_idx == Schema::kColumnNotFound) {
+ // Since the new predicate functions don't return errors, instead we create a special
+ // predicate that just returns the errors when we add it to the scanner.
+ //
+ // This allows the predicate API to be more "fluent".
+ return new KuduPredicate(new ErrorPredicateData(
+ Status::NotFound("column not found", col_name)));
+ }
+ return f(schema.column(col_idx));
+ }
+
sp::shared_ptr<KuduClient> client_;
const std::string name_;
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/src/kudu/common/column_predicate-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/common/column_predicate-test.cc b/src/kudu/common/column_predicate-test.cc
index 9f56186..d8a4ada 100644
--- a/src/kudu/common/column_predicate-test.cc
+++ b/src/kudu/common/column_predicate-test.cc
@@ -660,6 +660,81 @@ class TestColumnPredicate : public KuduTest {
ColumnPredicate::InList(column, &bot_list),
ColumnPredicate::InList(column, &res_list),
PredicateType::InList);
+
+ // IS NULL
+
+ // IS NULL AND
+ // None
+ // =
+ // None
+ TestMerge(ColumnPredicate::IsNull(column),
+ ColumnPredicate::None(column),
+ ColumnPredicate::None(column),
+ PredicateType::None);
+
+ // IS NULL AND
+ // |
+ // =
+ // None
+ TestMerge(ColumnPredicate::IsNull(column),
+ ColumnPredicate::Equality(column, &values[0]),
+ ColumnPredicate::None(column),
+ PredicateType::None);
+
+ // IS NULL AND
+ // [-------)
+ // =
+ // None
+ TestMerge(ColumnPredicate::IsNull(column),
+ ColumnPredicate::Range(column, &values[0], &values[2]),
+ ColumnPredicate::None(column),
+ PredicateType::None);
+
+ // IS NULL AND
+ // [------->
+ // =
+ // None
+ TestMerge(ColumnPredicate::IsNull(column),
+ ColumnPredicate::Range(column, &values[0], nullptr),
+ ColumnPredicate::None(column),
+ PredicateType::None);
+
+ // IS NULL AND
+ // <-------)
+ // =
+ // None
+ TestMerge(ColumnPredicate::IsNull(column),
+ ColumnPredicate::Range(column, nullptr, &values[2]),
+ ColumnPredicate::None(column),
+ PredicateType::None);
+
+ // IS NULL AND
+ // | | |
+ // =
+ // None
+ bot_list = { &values[1], &values[3], &values[6] };
+ TestMerge(ColumnPredicate::IsNull(column),
+ ColumnPredicate::InList(column, &bot_list),
+ ColumnPredicate::None(column),
+ PredicateType::None);
+
+ // IS NULL AND
+ // IS NOT NULL
+ // =
+ // None
+ TestMerge(ColumnPredicate::IsNull(column),
+ ColumnPredicate::IsNotNull(column),
+ ColumnPredicate::None(column),
+ PredicateType::None);
+
+ // IS NULL AND
+ // IS NULL
+ // =
+ // IS NULL
+ TestMerge(ColumnPredicate::IsNull(column),
+ ColumnPredicate::IsNull(column),
+ ColumnPredicate::IsNull(column),
+ PredicateType::IsNull);
}
};
@@ -979,13 +1054,16 @@ TEST_F(TestColumnPredicate, TestSelectivity) {
ColumnSchema column_s("d", STRING, true);
// Predicate type
+ ASSERT_LT(SelectivityComparator(ColumnPredicate::IsNull(column_i32),
+ ColumnPredicate::Equality(column_i32, &one_32)),
+ 0);
ASSERT_LT(SelectivityComparator(ColumnPredicate::Equality(column_i32, &one_32),
ColumnPredicate::Range(column_d, &one_d, nullptr)),
0);
ASSERT_LT(SelectivityComparator(ColumnPredicate::Equality(column_i32, &one_32),
ColumnPredicate::IsNotNull(column_s)),
0);
- ASSERT_LT(SelectivityComparator(ColumnPredicate::Range(column_i64, &one_64, nullptr),
+ ASSERT_LT(SelectivityComparator(ColumnPredicate::Range(column_i32, &one_32, nullptr),
ColumnPredicate::IsNotNull(column_i32)),
0);
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/src/kudu/common/column_predicate.cc
----------------------------------------------------------------------
diff --git a/src/kudu/common/column_predicate.cc b/src/kudu/common/column_predicate.cc
index 0e8c959..3b80104 100644
--- a/src/kudu/common/column_predicate.cc
+++ b/src/kudu/common/column_predicate.cc
@@ -143,6 +143,12 @@ ColumnPredicate ColumnPredicate::IsNotNull(ColumnSchema column) {
return ColumnPredicate(PredicateType::IsNotNull, move(column), nullptr, nullptr);
}
+ColumnPredicate ColumnPredicate::IsNull(ColumnSchema column) {
+ return column.is_nullable() ?
+ ColumnPredicate(PredicateType::IsNull, move(column), nullptr, nullptr) :
+ None(move(column));
+}
+
ColumnPredicate ColumnPredicate::None(ColumnSchema column) {
return ColumnPredicate(PredicateType::None, move(column), nullptr, nullptr);
}
@@ -159,6 +165,7 @@ void ColumnPredicate::Simplify() {
case PredicateType::None:
case PredicateType::Equality:
case PredicateType::IsNotNull: return;
+ case PredicateType::IsNull: return;
case PredicateType::Range: {
DCHECK(lower_ != nullptr || upper_ != nullptr);
if (lower_ != nullptr && upper_ != nullptr) {
@@ -229,18 +236,13 @@ void ColumnPredicate::Merge(const ColumnPredicate& other) {
return;
};
case PredicateType::IsNotNull: {
- // 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.
- predicate_type_ = other.predicate_type_;
- lower_ = other.lower_;
- upper_ = other.upper_;
- values_ = other.values_;
+ MergeIntoIsNotNull(other);
return;
};
+ case PredicateType::IsNull: {
+ MergeIntoIsNull(other);
+ return;
+ }
case PredicateType::InList: {
MergeIntoInList(other);
return;
@@ -288,6 +290,10 @@ void ColumnPredicate::MergeIntoRange(const ColumnPredicate& other) {
return;
};
case PredicateType::IsNotNull: return;
+ case PredicateType::IsNull: {
+ SetToNone();
+ return;
+ }
case PredicateType::InList : {
// The InList predicate values are examined to check whether
// they lie in the range.
@@ -333,6 +339,10 @@ void ColumnPredicate::MergeIntoEquality(const ColumnPredicate& other) {
return;
};
case PredicateType::IsNotNull: return;
+ case PredicateType::IsNull: {
+ SetToNone();
+ return;
+ }
case PredicateType::InList : {
// The equality value needs to be a member of the InList
if (!other.CheckValueInList(lower_)) {
@@ -344,12 +354,47 @@ void ColumnPredicate::MergeIntoEquality(const ColumnPredicate& other) {
LOG(FATAL) << "unknown predicate type";
}
+void ColumnPredicate::MergeIntoIsNotNull(const ColumnPredicate &other) {
+ CHECK(predicate_type_ == PredicateType::IsNotNull);
+ switch (other.predicate_type()) {
+ // The intersection of NULL and IS NOT NULL is None.
+ case PredicateType::IsNull: {
+ SetToNone();
+ return;
+ }
+ default: {
+ // Otherwise, the intersection is the other predicate.
+ predicate_type_ = other.predicate_type_;
+ lower_ = other.lower_;
+ upper_ = other.upper_;
+ values_ = other.values_;
+ return;
+ }
+ }
+}
+
+void ColumnPredicate::MergeIntoIsNull(const ColumnPredicate &other) {
+ CHECK(predicate_type_ == PredicateType::IsNull);
+ switch (other.predicate_type()) {
+ // The intersection of IS NULL and IS NULL is IS NULL.
+ case PredicateType::IsNull: {
+ return;
+ }
+ default: {
+ // Otherwise, the intersection is None.
+ // NB: This will not be true if NULL is allowed in an InList predicate.
+ SetToNone();
+ return;
+ }
+ }
+}
+
void ColumnPredicate::MergeIntoInList(const ColumnPredicate &other) {
CHECK(predicate_type_ == PredicateType::InList);
DCHECK(values_.size() > 1);
switch (other.predicate_type()) {
- case PredicateType::None : {
+ case PredicateType::None: {
SetToNone();
return;
};
@@ -388,6 +433,10 @@ void ColumnPredicate::MergeIntoInList(const ColumnPredicate &other) {
return;
}
case PredicateType::IsNotNull: return;
+ case PredicateType::IsNull: {
+ SetToNone();
+ return;
+ }
case PredicateType::InList: {
// Merge the 'other' IN list into this IN list. The basic idea is to loop
// through this predicate list, retaining only the values which are also
@@ -505,6 +554,20 @@ void ColumnPredicate::EvaluateForPhysicalType(const ColumnBlock& block,
}
return;
};
+ case PredicateType::IsNull: {
+ if (!block.is_nullable()) {
+ BitmapChangeBits(sel->mutable_bitmap(), 0, block.nrows(), false);
+ return;
+ }
+ // TODO(wdberkeley): make this more efficient by using bitwise operations on the
+ // null and selection vectors.
+ for (size_t i = 0; i < block.nrows(); i++) {
+ if (sel->IsRowSelected(i) && !block.is_null(i)) {
+ BitmapClear(sel->mutable_bitmap(), i);
+ }
+ }
+ return;
+ }
case PredicateType::InList: {
ApplyPredicate(block, sel, [this] (const void* cell) {
return std::binary_search(values_.begin(), values_.end(), cell,
@@ -559,6 +622,9 @@ string ColumnPredicate::ToString() const {
case PredicateType::IsNotNull: {
return strings::Substitute("`$0` IS NOT NULL", column_.name());
};
+ case PredicateType::IsNull: {
+ return strings::Substitute("`$0` IS NULL", column_.name());
+ };
case PredicateType::InList: {
string ss = "`";
ss.append(column_.name());
@@ -602,7 +668,8 @@ bool ColumnPredicate::operator==(const ColumnPredicate& other) const {
return true;
};
case PredicateType::None:
- case PredicateType::IsNotNull: return true;
+ case PredicateType::IsNotNull:
+ case PredicateType::IsNull: return true;
}
LOG(FATAL) << "unknown predicate type";
}
@@ -625,10 +692,11 @@ int SelectivityRank(const ColumnPredicate& predicate) {
int rank;
switch (predicate.predicate_type()) {
case PredicateType::None: rank = 0; break;
- case PredicateType::Equality: rank = 1; break;
- case PredicateType::InList: rank = 2; break;
- case PredicateType::Range: rank = 3; break;
- case PredicateType::IsNotNull: rank = 4; break;
+ case PredicateType::IsNull: rank = 1; break;
+ case PredicateType::Equality: rank = 2; break;
+ case PredicateType::InList: rank = 3; break;
+ case PredicateType::Range: rank = 4; break;
+ case PredicateType::IsNotNull: rank = 5; break;
default: LOG(FATAL) << "unknown predicate type";
}
return rank * (kLargestTypeSize + 1) + predicate.column().type_info()->size();
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/src/kudu/common/column_predicate.h
----------------------------------------------------------------------
diff --git a/src/kudu/common/column_predicate.h b/src/kudu/common/column_predicate.h
index aa996a1..d28f9b4 100644
--- a/src/kudu/common/column_predicate.h
+++ b/src/kudu/common/column_predicate.h
@@ -46,6 +46,9 @@ enum class PredicateType {
// A predicate which evaluates to true if the value is not null.
IsNotNull,
+ // A predicate which evaluates to true if the value is null.
+ IsNull,
+
// A predicate which evaluates to true if the column value is present in
// a value list.
InList,
@@ -112,12 +115,20 @@ class ColumnPredicate {
// Creates a new IS NOT NULL predicate for the column.
static ColumnPredicate IsNotNull(ColumnSchema column);
+ // Creates a new IS NULL predicate for the column.
+ //
+ // If the column is non-nullable, returns a None predicate instead.
+ static ColumnPredicate IsNull(ColumnSchema column);
+
// Create a new IN <LIST> predicate for the column.
//
// The values are not copied, and must outlive the returned predicate.
// The InList will be simplified into an Equality, Range or None if possible.
static ColumnPredicate InList(ColumnSchema column, std::vector<const void*>* values);
+ // Creates a new predicate which matches no values.
+ static ColumnPredicate None(ColumnSchema column);
+
// Returns the type of this predicate.
PredicateType predicate_type() const {
return predicate_type_;
@@ -172,6 +183,9 @@ class ColumnPredicate {
case PredicateType::IsNotNull: {
return true;
};
+ case PredicateType::IsNull: {
+ return false;
+ };
case PredicateType::InList: {
return std::binary_search(values_.begin(), values_.end(), cell,
[] (const void* lhs, const void* rhs) {
@@ -227,9 +241,6 @@ class ColumnPredicate {
ColumnSchema column,
std::vector<const void*>* values);
- // Creates a new predicate which matches no values.
- static ColumnPredicate None(ColumnSchema column);
-
// Transition to a None predicate type.
void SetToNone();
@@ -242,6 +253,12 @@ class ColumnPredicate {
// Merge another predicate into this Equality predicate.
void MergeIntoEquality(const ColumnPredicate& other);
+ // Merge another predicate into this IS NOT NULL predicate.
+ void MergeIntoIsNotNull(const ColumnPredicate& other);
+
+ // Merge another predicate into this IS NULL predicate.
+ void MergeIntoIsNull(const ColumnPredicate& other);
+
// Templated evaluation to inline the dispatch of comparator. Templating this
// allows dispatch to occur only once per batch.
template <DataType PhysicalType>
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/src/kudu/common/common.proto
----------------------------------------------------------------------
diff --git a/src/kudu/common/common.proto b/src/kudu/common/common.proto
index 5f84175..ff501f1 100644
--- a/src/kudu/common/common.proto
+++ b/src/kudu/common/common.proto
@@ -314,10 +314,13 @@ message ColumnPredicatePB {
message IsNotNull {}
+ message IsNull {}
+
oneof predicate {
Range range = 2;
Equality equality = 3;
IsNotNull is_not_null = 4;
InList in_list = 5;
+ IsNull is_null = 6;
}
}
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/src/kudu/common/key_util.cc
----------------------------------------------------------------------
diff --git a/src/kudu/common/key_util.cc b/src/kudu/common/key_util.cc
index 451cadd..11a5744 100644
--- a/src/kudu/common/key_util.cc
+++ b/src/kudu/common/key_util.cc
@@ -232,7 +232,8 @@ int PushUpperBoundKeyPredicates(ColIdxIter first,
break_loop = true;
}
break;
- case PredicateType::IsNotNull:
+ case PredicateType::IsNotNull: // Fallthrough intended
+ case PredicateType::IsNull:
break_loop = true;
break;
case PredicateType::InList:
@@ -293,7 +294,8 @@ int PushLowerBoundKeyPredicates(ColIdxIter first,
memcpy(row->mutable_cell_ptr(*col_idx_it), predicate->raw_lower(), size);
pushed_predicates++;
break;
- case PredicateType::IsNotNull:
+ case PredicateType::IsNotNull: // Fallthrough intended
+ case PredicateType::IsNull:
break_loop = true;
break;
case PredicateType::InList:
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/src/kudu/common/scan_spec.cc
----------------------------------------------------------------------
diff --git a/src/kudu/common/scan_spec.cc b/src/kudu/common/scan_spec.cc
index 3f3ad07..46acc08 100644
--- a/src/kudu/common/scan_spec.cc
+++ b/src/kudu/common/scan_spec.cc
@@ -73,6 +73,7 @@ void ScanSpec::SetLowerBoundKey(const EncodedKey* key) {
lower_bound_key_ = key;
}
}
+
void ScanSpec::SetExclusiveUpperBoundKey(const EncodedKey* key) {
if (exclusive_upper_bound_key_ == nullptr ||
key->encoded_key().compare(exclusive_upper_bound_key_->encoded_key()) < 0) {
http://git-wip-us.apache.org/repos/asf/kudu/blob/431aee53/src/kudu/common/wire_protocol.cc
----------------------------------------------------------------------
diff --git a/src/kudu/common/wire_protocol.cc b/src/kudu/common/wire_protocol.cc
index 361be0d..fd61231 100644
--- a/src/kudu/common/wire_protocol.cc
+++ b/src/kudu/common/wire_protocol.cc
@@ -385,6 +385,10 @@ void ColumnPredicateToPB(const ColumnPredicate& predicate,
pb->mutable_is_not_null();
return;
};
+ case PredicateType::IsNull: {
+ pb->mutable_is_null();
+ return;
+ }
case PredicateType::InList: {
auto* values = pb->mutable_in_list()->mutable_values();
for (const void* value : predicate.raw_values()) {
@@ -454,10 +458,13 @@ Status ColumnPredicateFromPB(const Schema& schema,
break;
};
case ColumnPredicatePB::kIsNotNull: {
- ColumnPredicate p = ColumnPredicate::IsNotNull(col);
*predicate = ColumnPredicate::IsNotNull(col);
break;
};
+ case ColumnPredicatePB::kIsNull: {
+ *predicate = ColumnPredicate::IsNull(col);
+ break;
+ }
default: return Status::InvalidArgument("Unknown predicate type for column", col.name());
}
return Status::OK();