diff --git a/docs/content/querying/searchqueryspec.md b/docs/content/querying/searchqueryspec.md index 4627bbbec08..53773005ff5 100644 --- a/docs/content/querying/searchqueryspec.md +++ b/docs/content/querying/searchqueryspec.md @@ -40,4 +40,16 @@ If any part of a dimension value contains the value specified in this search que "case_sensitive" : true, "value" : "some_value" } +``` + +RegexSearchQuerySpec +---------------------------------- + +If any part of a dimension value contains the pattern specified in this search query spec, a "match" occurs. The grammar is: + +```json +{ + "type" : "regex", + "pattern" : "some_pattern" +} ``` \ No newline at end of file diff --git a/processing/src/main/java/io/druid/query/search/search/RegexSearchQuerySpec.java b/processing/src/main/java/io/druid/query/search/search/RegexSearchQuerySpec.java new file mode 100644 index 00000000000..2f4b7928d56 --- /dev/null +++ b/processing/src/main/java/io/druid/query/search/search/RegexSearchQuerySpec.java @@ -0,0 +1,104 @@ +/* +* 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.search.search; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Preconditions; +import com.metamx.common.StringUtils; + +import java.nio.ByteBuffer; +import java.util.regex.Pattern; + + +public class RegexSearchQuerySpec implements SearchQuerySpec +{ + + private static final byte CACHE_TYPE_ID = 0x3; + + private final String pattern; + private final Pattern compiled; + + @JsonCreator + public RegexSearchQuerySpec( + @JsonProperty("pattern") String pattern + ) + { + this.pattern = Preconditions.checkNotNull(pattern, "pattern should not be null"); + compiled = Pattern.compile(pattern); + } + + @JsonProperty + public String getPattern() + { + return pattern; + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (!(o instanceof RegexSearchQuerySpec)) { + return false; + } + + RegexSearchQuerySpec that = (RegexSearchQuerySpec) o; + + return pattern.equals(that.pattern); + + } + + @Override + public int hashCode() + { + return pattern.hashCode(); + } + + @Override + public boolean accept(String dimVal) + { + if (dimVal == null) { + return false; + } + + return compiled.matcher(dimVal).find(); + } + + @Override + public byte[] getCacheKey() + { + byte[] patternBytes = StringUtils.toUtf8(pattern); + + return ByteBuffer.allocate(1 + patternBytes.length) + .put(CACHE_TYPE_ID) + .put(patternBytes) + .array(); + } + + @Override + public String toString() + { + return "RegexSearchQuerySpec{" + + "pattern=" + pattern + "}"; + } + +} diff --git a/processing/src/main/java/io/druid/query/search/search/SearchQuerySpec.java b/processing/src/main/java/io/druid/query/search/search/SearchQuerySpec.java index 2dcc2ecbae4..80794b73f4c 100644 --- a/processing/src/main/java/io/druid/query/search/search/SearchQuerySpec.java +++ b/processing/src/main/java/io/druid/query/search/search/SearchQuerySpec.java @@ -26,7 +26,8 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; @JsonSubTypes(value = { @JsonSubTypes.Type(name = "contains", value = ContainsSearchQuerySpec.class), @JsonSubTypes.Type(name = "insensitive_contains", value = InsensitiveContainsSearchQuerySpec.class), - @JsonSubTypes.Type(name = "fragment", value = FragmentSearchQuerySpec.class) + @JsonSubTypes.Type(name = "fragment", value = FragmentSearchQuerySpec.class), + @JsonSubTypes.Type(name = "regex", value = RegexSearchQuerySpec.class) }) public interface SearchQuerySpec { diff --git a/processing/src/test/java/io/druid/query/search/RegexSearchQueryTest.java b/processing/src/test/java/io/druid/query/search/RegexSearchQueryTest.java new file mode 100644 index 00000000000..ab6a1c4fc32 --- /dev/null +++ b/processing/src/test/java/io/druid/query/search/RegexSearchQueryTest.java @@ -0,0 +1,52 @@ +/* +* 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.search; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.druid.jackson.DefaultObjectMapper; +import io.druid.query.search.search.RegexSearchQuerySpec; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; + +public class RegexSearchQueryTest +{ + private static final ObjectMapper jsonMapper = new DefaultObjectMapper(); + + @Test + public void testQuerySerialization() throws IOException + { + RegexSearchQuerySpec spec = new RegexSearchQuerySpec("(upfront|total_market)"); + + String json = jsonMapper.writeValueAsString(spec); + RegexSearchQuerySpec serdeQuery = (RegexSearchQuerySpec) jsonMapper.readValue(json, RegexSearchQuerySpec.class); + + Assert.assertEquals(spec, serdeQuery); + } + + @Test + public void testRegexCompare() + { + RegexSearchQuerySpec rsq = new RegexSearchQuerySpec("^a.*b"); + Assert.assertTrue(rsq.accept("aabb")); + Assert.assertFalse(rsq.accept("babba")); + } +}