From 0ee861d0daf17a0ef8c2dc8c9615700e9dee1ee0 Mon Sep 17 00:00:00 2001 From: Charles Allen Date: Fri, 18 Mar 2016 22:16:26 -0700 Subject: [PATCH] Add ExtractionFn to LookupExtractor bridge --- .../druid/query/extraction/ExtractionFn.java | 2 + .../lookup/RegisteredLookupExtractionFn.java | 167 ++++++++++++ .../RegisteredLookupExtractionFnTest.java | 256 ++++++++++++++++++ 3 files changed, 425 insertions(+) create mode 100644 processing/src/main/java/io/druid/query/lookup/RegisteredLookupExtractionFn.java create mode 100644 processing/src/test/java/io/druid/query/lookup/RegisteredLookupExtractionFnTest.java diff --git a/processing/src/main/java/io/druid/query/extraction/ExtractionFn.java b/processing/src/main/java/io/druid/query/extraction/ExtractionFn.java index fd1de09fd44..f5c72c6b860 100644 --- a/processing/src/main/java/io/druid/query/extraction/ExtractionFn.java +++ b/processing/src/main/java/io/druid/query/extraction/ExtractionFn.java @@ -22,6 +22,7 @@ package io.druid.query.extraction; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import io.druid.query.lookup.LookupExtractionFn; +import io.druid.query.lookup.RegisteredLookupExtractionFn; /** */ @@ -35,6 +36,7 @@ import io.druid.query.lookup.LookupExtractionFn; @JsonSubTypes.Type(name = "timeFormat", value = TimeFormatExtractionFn.class), @JsonSubTypes.Type(name = "identity", value = IdentityExtractionFn.class), @JsonSubTypes.Type(name = "lookup", value = LookupExtractionFn.class), + @JsonSubTypes.Type(name = "registeredLookup", value = RegisteredLookupExtractionFn.class), @JsonSubTypes.Type(name = "substring", value = SubstringDimExtractionFn.class), @JsonSubTypes.Type(name = "cascade", value = CascadeExtractionFn.class), @JsonSubTypes.Type(name = "stringFormat", value = StringFormatExtractionFn.class), diff --git a/processing/src/main/java/io/druid/query/lookup/RegisteredLookupExtractionFn.java b/processing/src/main/java/io/druid/query/lookup/RegisteredLookupExtractionFn.java new file mode 100644 index 00000000000..a31ed6bd7e1 --- /dev/null +++ b/processing/src/main/java/io/druid/query/lookup/RegisteredLookupExtractionFn.java @@ -0,0 +1,167 @@ +/* + * Licensed to Metamarkets Group Inc. (Metamarkets) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Metamarkets 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 io.druid.query.lookup; + +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Preconditions; +import io.druid.query.extraction.ExtractionFn; + +import javax.annotation.Nullable; + +public class RegisteredLookupExtractionFn implements ExtractionFn +{ + private final LookupExtractionFn delegate; + private final String name; + + RegisteredLookupExtractionFn(LookupExtractionFn delegate, String name) + { + this.delegate = delegate; + this.name = name; + } + + @JsonCreator + public static RegisteredLookupExtractionFn create( + @JacksonInject LookupReferencesManager manager, + @JsonProperty("lookup") String lookup, + @JsonProperty("retainMissingValue") final boolean retainMissingValue, + @Nullable @JsonProperty("replaceMissingValueWith") final String replaceMissingValueWith, + @JsonProperty("injective") final boolean injective, + @JsonProperty("optimize") Boolean optimize + ) + { + Preconditions.checkArgument(lookup != null, "`lookup` required"); + final LookupExtractorFactory factory = manager.get(lookup); + Preconditions.checkNotNull(factory, "lookup [%s] not found", lookup); + return new RegisteredLookupExtractionFn( + new LookupExtractionFn( + factory.get(), + retainMissingValue, + replaceMissingValueWith, + injective, + optimize + ), + lookup + ); + } + + @JsonProperty("lookup") + public String getLookup() + { + return name; + } + + @JsonProperty("retainMissingValue") + public boolean isRetainMissingValue() + { + return delegate.isRetainMissingValue(); + } + + @JsonProperty("replaceMissingValueWith") + public String getReplaceMissingValueWith() + { + return delegate.getReplaceMissingValueWith(); + } + + @JsonProperty("injective") + public boolean isInjective() + { + return delegate.isInjective(); + } + + @JsonProperty("optimize") + public boolean isOptimize() + { + return delegate.isOptimize(); + } + + @Override + public byte[] getCacheKey() + { + return delegate.getCacheKey(); + } + + @Override + public String apply(Object value) + { + return delegate.apply(value); + } + + @Override + public String apply(String value) + { + return delegate.apply(value); + } + + @Override + public String apply(long value) + { + return delegate.apply(value); + } + + @Override + public boolean preservesOrdering() + { + return delegate.preservesOrdering(); + } + + @Override + public ExtractionType getExtractionType() + { + return delegate.getExtractionType(); + } + + @Override + public String toString() + { + return "RegisteredLookupExtractionFn{" + + "delegate=" + delegate + + ", name='" + name + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + RegisteredLookupExtractionFn that = (RegisteredLookupExtractionFn) o; + + if (!delegate.equals(that.delegate)) { + return false; + } + return name.equals(that.name); + + } + + @Override + public int hashCode() + { + int result = delegate.hashCode(); + result = 31 * result + name.hashCode(); + return result; + } +} diff --git a/processing/src/test/java/io/druid/query/lookup/RegisteredLookupExtractionFnTest.java b/processing/src/test/java/io/druid/query/lookup/RegisteredLookupExtractionFnTest.java new file mode 100644 index 00000000000..6d6e5d529af --- /dev/null +++ b/processing/src/test/java/io/druid/query/lookup/RegisteredLookupExtractionFnTest.java @@ -0,0 +1,256 @@ +/* + * Licensed to Metamarkets Group Inc. (Metamarkets) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Metamarkets 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 io.druid.query.lookup; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; +import io.druid.jackson.DefaultObjectMapper; +import io.druid.query.extraction.MapLookupExtractor; +import org.easymock.EasyMock; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Map; + +public class RegisteredLookupExtractionFnTest +{ + private static Map MAP = ImmutableMap.of( + "foo", "bar", + "bat", "baz" + ); + private static final LookupExtractor LOOKUP_EXTRACTOR = new MapLookupExtractor(MAP, true); + private static final String LOOKUP_NAME = "some lookup"; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void testSimpleDelegation() + { + final LookupReferencesManager manager = EasyMock.createStrictMock(LookupReferencesManager.class); + managerReturnsMap(manager); + EasyMock.replay(manager); + final RegisteredLookupExtractionFn fn = RegisteredLookupExtractionFn.create( + manager, + LOOKUP_NAME, + true, + null, + true, + false + ); + EasyMock.verify(manager); + for (String orig : Arrays.asList("", "foo", "bat")) { + Assert.assertEquals(LOOKUP_EXTRACTOR.apply(orig), fn.apply(orig)); + } + Assert.assertEquals("not in the map", fn.apply("not in the map")); + } + + + @Test + public void testMissingDelegation() + { + final LookupReferencesManager manager = EasyMock.createStrictMock(LookupReferencesManager.class); + EasyMock.expect(manager.get(EasyMock.eq(LOOKUP_NAME))).andReturn(null).once(); + EasyMock.replay(manager); + + expectedException.expectMessage("lookup [some lookup] not found"); + try { + RegisteredLookupExtractionFn.create( + manager, + LOOKUP_NAME, + true, + null, + true, + false + ); + } + finally { + EasyMock.verify(manager); + } + } + + @Test + public void testNullLookup() + { + expectedException.expectMessage("`lookup` required"); + RegisteredLookupExtractionFn.create( + null, + null, + true, + null, + true, + false + ); + } + + @Test + public void testSerDe() throws Exception + { + final ObjectMapper mapper = new DefaultObjectMapper(); + + final LookupReferencesManager manager = EasyMock.createStrictMock(LookupReferencesManager.class); + managerReturnsMap(manager); + EasyMock.replay(manager); + final RegisteredLookupExtractionFn fn = RegisteredLookupExtractionFn.create( + manager, + LOOKUP_NAME, + true, + null, + true, + false + ); + EasyMock.verify(manager); + + final TypeReference> typeReference = new TypeReference>() + { + }; + final Map result = mapper.readValue(mapper.writeValueAsString(fn), typeReference); + Assert.assertEquals(mapper.convertValue(fn, typeReference), result); + Assert.assertEquals(LOOKUP_NAME, result.get("lookup")); + Assert.assertEquals(true, result.get("retainMissingValue")); + Assert.assertEquals(true, result.get("injective")); + Assert.assertNull(result.get("replaceMissingValueWith")); + Assert.assertEquals(false, result.get("optimize")); + } + + @Test + public void testEquals() + { + final LookupReferencesManager manager = EasyMock.createStrictMock(LookupReferencesManager.class); + managerReturnsMap(manager); + EasyMock.replay(manager); + final RegisteredLookupExtractionFn fn = RegisteredLookupExtractionFn.create( + manager, + LOOKUP_NAME, + false, + "something", + true, + false + ); + Assert.assertEquals( + fn, + RegisteredLookupExtractionFn.create( + manager, + LOOKUP_NAME, + false, + "something", + true, + false + ) + ); + Assert.assertNotEquals( + fn, + RegisteredLookupExtractionFn.create( + manager, + LOOKUP_NAME, + true, + null, + true, + false + ) + ); + + Assert.assertNotEquals( + fn, + RegisteredLookupExtractionFn.create( + manager, + LOOKUP_NAME, + false, + "something else", + true, + false + ) + ); + + + Assert.assertNotEquals( + fn, + RegisteredLookupExtractionFn.create( + manager, + LOOKUP_NAME, + false, + "something", + false, + false + ) + ); + + Assert.assertNotEquals( + fn, + RegisteredLookupExtractionFn.create( + manager, + LOOKUP_NAME, + false, + "something", + true, + true + ) + ); + + + Assert.assertNotEquals( + fn, + RegisteredLookupExtractionFn.create( + manager, + LOOKUP_NAME, + false, + null, + true, + false + ) + ); + EasyMock.verify(manager); + } + + private void managerReturnsMap(LookupReferencesManager manager) + { + EasyMock.expect(manager.get(EasyMock.eq(LOOKUP_NAME))).andReturn(new LookupExtractorFactory() + { + @Override + public boolean start() + { + return false; + } + + @Override + public boolean close() + { + return false; + } + + @Override + public boolean replaces(@Nullable LookupExtractorFactory other) + { + return false; + } + + @Override + public LookupExtractor get() + { + return LOOKUP_EXTRACTOR; + } + }).anyTimes(); + } +}