Skip to content

Commit 23dd98c

Browse files
authored
Closes #36. Support ContainsKey and ContainsValue (#48)
1 parent 15c7a90 commit 23dd98c

File tree

8 files changed

+375
-65
lines changed

8 files changed

+375
-65
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
3737
<maven.compiler.source>1.8</maven.compiler.source>
3838
<maven.compiler.target>1.8</maven.compiler.target>
39-
<bullet.record.version>0.2.0</bullet.record.version>
39+
<bullet.record.version>0.2.1</bullet.record.version>
4040
<sketches.version>0.9.1</sketches.version>
4141
</properties>
4242

src/main/java/com/yahoo/bullet/parsing/Clause.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ public enum Operation {
4141
REGEX_LIKE,
4242
@SerializedName("SIZEOF")
4343
SIZE_OF,
44+
@SerializedName("CONTAINSKEY")
45+
CONTAINS_KEY,
46+
@SerializedName("CONTAINSVALUE")
47+
CONTAINS_VALUE,
4448
@SerializedName("AND")
4549
AND,
4650
@SerializedName("OR")
@@ -49,7 +53,7 @@ public enum Operation {
4953
NOT;
5054

5155
public static final List<String> LOGICALS = asList("AND", "OR", "NOT");
52-
public static final List<String> RELATIONALS = asList("==", "!=", ">=", "<=", ">", "<", "RLIKE", "SIZEOF");
56+
public static final List<String> RELATIONALS = asList("==", "!=", ">=", "<=", ">", "<", "RLIKE", "SIZEOF", "CONTAINSKEY", "CONTAINSVALUE");
5357
}
5458

5559
@Expose

src/main/java/com/yahoo/bullet/querying/FilterOperations.java

Lines changed: 41 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import static com.yahoo.bullet.common.Utilities.isEmpty;
2929
import static com.yahoo.bullet.typesystem.TypedObject.GENERIC_UNKNOWN;
3030
import static com.yahoo.bullet.typesystem.TypedObject.IS_NOT_NULL;
31-
import static com.yahoo.bullet.typesystem.TypedObject.IS_NOT_UNKNOWN;
31+
import static com.yahoo.bullet.typesystem.TypedObject.IS_PRIMITIVE_OR_NULL;
3232

3333
@Slf4j
3434
public class FilterOperations {
@@ -56,14 +56,16 @@ public interface LogicalOperator extends BiPredicate<BulletRecord, Stream<Boolea
5656
// SOME_LONG_VALUE EQ [1.23, 35.2] will be false
5757
// SOME_LONG_VALUE NE [1.23. 425.3] will be false
5858
// SOME_LONG_VALUE GT/LT/GE/LE [12.4, 253.4] will be false! even if SOME_LONG_VALUE numerically could make it true.
59-
private static final Comparator<TypedObject> EQ = (t, s) -> s.anyMatch(i -> t.compareTo(i) == 0);
60-
private static final Comparator<TypedObject> NE = (t, s) -> s.noneMatch(i -> t.compareTo(i) == 0);
59+
private static final Comparator<TypedObject> EQ = (t, s) -> s.anyMatch(t::equalTo);
60+
private static final Comparator<TypedObject> NE = (t, s) -> s.noneMatch(t::equalTo);
6161
private static final Comparator<TypedObject> GT = (t, s) -> s.anyMatch(i -> t.compareTo(i) > 0);
6262
private static final Comparator<TypedObject> LT = (t, s) -> s.anyMatch(i -> t.compareTo(i) < 0);
6363
private static final Comparator<TypedObject> GE = (t, s) -> s.anyMatch(i -> t.compareTo(i) >= 0);
6464
private static final Comparator<TypedObject> LE = (t, s) -> s.anyMatch(i -> t.compareTo(i) <= 0);
6565
private static final Comparator<Pattern> RLIKE = (t, s) -> s.map(p -> p.matcher(t.toString())).anyMatch(Matcher::matches);
66-
private static final Comparator<TypedObject> SIZEOF = (t, s) -> s.anyMatch(i -> sizeOf(t) == i.getValue());
66+
private static final Comparator<TypedObject> SIZEOF = (t, s) -> s.anyMatch(i -> i.equalTo(t.size()));
67+
private static final Comparator<TypedObject> CONTAINSKEY = (t, s) -> s.anyMatch(i -> t.containsKey((String) i.getValue()));
68+
private static final Comparator<TypedObject> CONTAINSVALUE = (t, s) -> s.anyMatch(t::containsValue);
6769
private static final LogicalOperator AND = (r, s) -> s.allMatch(Boolean::valueOf);
6870
private static final LogicalOperator OR = (r, s) -> s.anyMatch(Boolean::valueOf);
6971
private static final LogicalOperator NOT = (r, s) -> !s.findFirst().get();
@@ -76,9 +78,11 @@ public interface LogicalOperator extends BiPredicate<BulletRecord, Stream<Boolea
7678
COMPARATORS.put(Clause.Operation.LESS_THAN, isNotNullAnd(LT));
7779
COMPARATORS.put(Clause.Operation.GREATER_EQUALS, isNotNullAnd(GE));
7880
COMPARATORS.put(Clause.Operation.LESS_EQUALS, isNotNullAnd(LE));
81+
COMPARATORS.put(Clause.Operation.SIZE_OF, isNotNullAnd(SIZEOF));
82+
COMPARATORS.put(Clause.Operation.CONTAINS_KEY, isNotNullAnd(CONTAINSKEY));
83+
COMPARATORS.put(Clause.Operation.CONTAINS_VALUE, isNotNullAnd(CONTAINSVALUE));
7984
}
8085
static final Comparator<Pattern> REGEX_LIKE = isNotNullAnd(RLIKE);
81-
static final Comparator<TypedObject> SIZE_OF = isNotNullAnd(SIZEOF);
8286
static final Map<Clause.Operation, LogicalOperator> LOGICAL_OPERATORS = new EnumMap<>(Clause.Operation.class);
8387
static {
8488
LOGICAL_OPERATORS.put(Clause.Operation.AND, AND);
@@ -89,40 +93,26 @@ public interface LogicalOperator extends BiPredicate<BulletRecord, Stream<Boolea
8993
/**
9094
* Exposed for testing. Cast the values to the type of the object if possible.
9195
*
92-
* @param object The {@link TypedObject} to cast the values to.
96+
* @param type The {@link Type} to cast the values to.
9397
* @param values The {@link List} of values to try and cast to the object.
9498
* @return A {@link Stream} of casted {@link TypedObject}.
9599
*/
96-
static Stream<TypedObject> cast(BulletRecord record, TypedObject object, List<ObjectFilterClause.Value> values) {
97-
return values.stream().filter(Objects::nonNull).map(v -> getValue(record, object, v)).filter(IS_NOT_UNKNOWN);
100+
static Stream<TypedObject> cast(BulletRecord record, Type type, List<ObjectFilterClause.Value> values) {
101+
return values.stream().filter(Objects::nonNull).map(v -> getTypedValue(record, type, v)).filter(IS_PRIMITIVE_OR_NULL);
98102
}
99103

100104
private static <T> Comparator<T> isNotNullAnd(Comparator<T> comparator) {
101105
return (t, s) -> IS_NOT_NULL.test(t) && comparator.compare(t, s);
102106
}
103107

104-
private static Integer sizeOf(TypedObject object) {
105-
Object o = object.getValue();
106-
if (o instanceof List) {
107-
return List.class.cast(o).size();
108-
}
109-
if (o instanceof Map) {
110-
return Map.class.cast(o).size();
111-
}
112-
if (o instanceof String) {
113-
return String.class.cast(o).length();
114-
}
115-
return 1;
116-
}
117-
118-
private static TypedObject getValue(BulletRecord record, TypedObject object, ObjectFilterClause.Value value) {
108+
private static TypedObject getTypedValue(BulletRecord record, Type type, ObjectFilterClause.Value value) {
119109
switch (value.getKind()) {
120110
case FIELD:
121-
return object.typeCastFromObject(extractField(value.getValue(), record));
111+
return TypedObject.typeCastFromObject(type, extractField(value.getValue(), record));
122112
case VALUE:
123113
// Right now, we cast the filter values which are lists of strings to the value being filtered on's type.
124114
// In the future, we might want to support providing non-String values.
125-
return object.typeCast(value.getValue());
115+
return TypedObject.typeCast(type, value.getValue());
126116
default:
127117
log.error("Unsupported value kind: " + value.getKind().name());
128118
return GENERIC_UNKNOWN;
@@ -135,14 +125,21 @@ private static boolean performRelational(BulletRecord record, ObjectFilterClause
135125
return true;
136126
}
137127
TypedObject object = extractTypedObject(clause.getField(), record);
138-
switch (operator) {
139-
case REGEX_LIKE:
140-
return REGEX_LIKE.compare(object, clause.getPatterns().stream());
141-
case SIZE_OF:
142-
return SIZE_OF.compare(object, cast(record, new TypedObject(Type.INTEGER, 0), clause.getValues()));
143-
default:
144-
return COMPARATORS.get(operator).compare(object, cast(record, object, clause.getValues()));
128+
if (operator == Clause.Operation.REGEX_LIKE) {
129+
return REGEX_LIKE.compare(object, clause.getPatterns().stream());
145130
}
131+
132+
Type type;
133+
if (operator == Clause.Operation.SIZE_OF) {
134+
type = Type.INTEGER;
135+
} else if (operator == Clause.Operation.CONTAINS_KEY) {
136+
type = Type.STRING;
137+
} else if (operator == Clause.Operation.CONTAINS_VALUE) {
138+
type = object.getPrimitiveType();
139+
} else {
140+
type = object.getType();
141+
}
142+
return COMPARATORS.get(operator).compare(object, cast(record, type, clause.getValues()));
146143
}
147144

148145
private static boolean performRelational(BulletRecord record, StringFilterClause clause) {
@@ -170,12 +167,18 @@ public static boolean perform(BulletRecord record, Clause clause) {
170167
// cost of violating polymorphism in this one spot.
171168
// We do not want processing logic in FilterClause or LogicalClause, otherwise we could put the appropriate
172169
// methods in those classes.
173-
if (clause instanceof ObjectFilterClause) {
174-
return performRelational(record, (ObjectFilterClause) clause);
175-
} else if (clause instanceof StringFilterClause) {
176-
return performRelational(record, (StringFilterClause) clause);
177-
} else {
178-
return performLogical(record, (LogicalClause) clause);
170+
try {
171+
if (clause instanceof ObjectFilterClause) {
172+
return performRelational(record, (ObjectFilterClause) clause);
173+
} else if (clause instanceof StringFilterClause) {
174+
return performRelational(record, (StringFilterClause) clause);
175+
} else {
176+
return performLogical(record, (LogicalClause) clause);
177+
}
178+
} catch (RuntimeException e) {
179+
log.error("Unable to perform filter {} to record {}", clause, record);
180+
log.error("Skipping due to", e);
181+
return false;
179182
}
180183
}
181184
}

src/main/java/com/yahoo/bullet/typesystem/Type.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public enum Type {
2727

2828
public static final String NULL_EXPRESSION = "null";
2929
public static List<Type> PRIMITIVES = Arrays.asList(BOOLEAN, INTEGER, LONG, FLOAT, DOUBLE, STRING);
30+
public static List<Type> SUPPORTED_TYPES = Arrays.asList(BOOLEAN, INTEGER, LONG, FLOAT, DOUBLE, STRING, LIST, MAP);
3031
public static List<Type> NUMERICS = Arrays.asList(INTEGER, LONG, FLOAT, DOUBLE);
3132

3233
private final Class underlyingType;
@@ -41,7 +42,7 @@ public enum Type {
4142
}
4243

4344
/**
44-
* Tries to get the type of a given object from {@link #PRIMITIVES}.
45+
* Tries to get the type of a given object from {@link #SUPPORTED_TYPES}.
4546
*
4647
* @param object The object whose type is to be determined.
4748
* @return {@link Type} for this object, the {@link Type#NULL} if the object was null or {@link Type#UNKNOWN}
@@ -52,8 +53,7 @@ public static Type getType(Object object) {
5253
return Type.NULL;
5354
}
5455

55-
// Only support the atomic, primitive types for now since all our operations are on atomic types.
56-
for (Type type : PRIMITIVES) {
56+
for (Type type : SUPPORTED_TYPES) {
5757
if (type.getUnderlyingType().isInstance(object)) {
5858
return type;
5959
}
@@ -85,14 +85,28 @@ public Object cast(String value) {
8585
return value;
8686
case NULL:
8787
return value == null || NULL_EXPRESSION.compareToIgnoreCase(value) == 0 ? null : value;
88-
case UNKNOWN:
89-
return value;
9088
// We won't support the rest for castability. This wouldn't happen if getType was used to create
91-
// TypedObjects because because we only support PRIMITIVES and UNKNOWN
89+
// TypedObjects because because we only support cast operation on PRIMITIVES and NULL.
9290
default:
9391
throw new ClassCastException("Cannot cast " + value + " to type " + this);
9492
}
9593
}
9694

95+
/**
96+
* Takes an object and casts it to this type.
97+
*
98+
* @param object The object that is being cast.
99+
* @return The casted object.
100+
* @throws ClassCastException if the cast cannot be done.
101+
*/
102+
public Object castObject(Object object) throws ClassCastException {
103+
if (this == LONG && object instanceof Integer) {
104+
return ((Integer) object).longValue();
105+
} else if (this == DOUBLE && object instanceof Float) {
106+
return ((Float) object).doubleValue();
107+
} else {
108+
return underlyingType.cast(object);
109+
}
110+
}
97111
}
98112

0 commit comments

Comments
 (0)