From e01ed160308f2ca4c165b7d503aaa82878e17de1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Tue, 10 Mar 2015 15:09:59 -0700 Subject: [PATCH] serde tests + equals/hashCode fixes for extraction functions --- .../extraction/JavascriptExtractionFn.java | 28 +++++ .../extraction/MatchingDimExtractionFn.java | 28 +++++ .../extraction/RegexDimExtractionFn.java | 28 +++++ .../SearchQuerySpecDimExtractionFn.java | 28 +++++ .../query/extraction/TimeDimExtractionFn.java | 34 ++++++ .../extraction/TimeFormatExtractionFn.java | 34 ++++++ .../ExtractionDimensionSpecTest.java | 112 ++++++++++++++++++ .../TimeFormatExtractionFnTest.java | 27 ++++- .../JavascriptExtractionFnTest.java | 21 ++++ .../MatchingDimExtractionFnTest.java | 21 ++++ .../extraction/RegexDimExtractionFnTest.java | 21 ++++ .../extraction/TimeDimExtractionFnTest.java | 23 ++++ 12 files changed, 403 insertions(+), 2 deletions(-) create mode 100644 processing/src/test/java/io/druid/query/dimension/ExtractionDimensionSpecTest.java diff --git a/processing/src/main/java/io/druid/query/extraction/JavascriptExtractionFn.java b/processing/src/main/java/io/druid/query/extraction/JavascriptExtractionFn.java index 8c698b95053..e3563b2942f 100644 --- a/processing/src/main/java/io/druid/query/extraction/JavascriptExtractionFn.java +++ b/processing/src/main/java/io/druid/query/extraction/JavascriptExtractionFn.java @@ -20,6 +20,7 @@ package io.druid.query.extraction; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Function; +import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.metamx.common.StringUtils; import org.mozilla.javascript.Context; @@ -67,6 +68,8 @@ public class JavascriptExtractionFn implements ExtractionFn @JsonProperty("function") String function ) { + Preconditions.checkNotNull(function, "function must not be null"); + this.function = function; this.fn = compile(function); } @@ -118,4 +121,29 @@ public class JavascriptExtractionFn implements ExtractionFn "function='" + function + '\'' + '}'; } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + JavascriptExtractionFn that = (JavascriptExtractionFn) o; + + if (!function.equals(that.function)) { + return false; + } + + return true; + } + + @Override + public int hashCode() + { + return function.hashCode(); + } } diff --git a/processing/src/main/java/io/druid/query/extraction/MatchingDimExtractionFn.java b/processing/src/main/java/io/druid/query/extraction/MatchingDimExtractionFn.java index 07f4c38413c..bd025beeb2f 100644 --- a/processing/src/main/java/io/druid/query/extraction/MatchingDimExtractionFn.java +++ b/processing/src/main/java/io/druid/query/extraction/MatchingDimExtractionFn.java @@ -19,6 +19,7 @@ package io.druid.query.extraction; 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; @@ -39,6 +40,8 @@ public class MatchingDimExtractionFn extends DimExtractionFn @JsonProperty("expr") String expr ) { + Preconditions.checkNotNull(expr, "expr must not be null"); + this.expr = expr; this.pattern = Pattern.compile(expr); } @@ -78,4 +81,29 @@ public class MatchingDimExtractionFn extends DimExtractionFn { return String.format("regex_matches(%s)", expr); } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + MatchingDimExtractionFn that = (MatchingDimExtractionFn) o; + + if (!expr.equals(that.expr)) { + return false; + } + + return true; + } + + @Override + public int hashCode() + { + return expr.hashCode(); + } } diff --git a/processing/src/main/java/io/druid/query/extraction/RegexDimExtractionFn.java b/processing/src/main/java/io/druid/query/extraction/RegexDimExtractionFn.java index 08ff946fabd..3a9d3b131d1 100644 --- a/processing/src/main/java/io/druid/query/extraction/RegexDimExtractionFn.java +++ b/processing/src/main/java/io/druid/query/extraction/RegexDimExtractionFn.java @@ -19,6 +19,7 @@ package io.druid.query.extraction; 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; @@ -39,6 +40,8 @@ public class RegexDimExtractionFn extends DimExtractionFn @JsonProperty("expr") String expr ) { + Preconditions.checkNotNull(expr, "expr must not be null"); + this.expr = expr; this.pattern = Pattern.compile(expr); } @@ -77,4 +80,29 @@ public class RegexDimExtractionFn extends DimExtractionFn { return String.format("regex(%s)", expr); } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + RegexDimExtractionFn that = (RegexDimExtractionFn) o; + + if (!expr.equals(that.expr)) { + return false; + } + + return true; + } + + @Override + public int hashCode() + { + return expr.hashCode(); + } } diff --git a/processing/src/main/java/io/druid/query/extraction/SearchQuerySpecDimExtractionFn.java b/processing/src/main/java/io/druid/query/extraction/SearchQuerySpecDimExtractionFn.java index 9d8fe3f8e03..2e5743825ab 100644 --- a/processing/src/main/java/io/druid/query/extraction/SearchQuerySpecDimExtractionFn.java +++ b/processing/src/main/java/io/druid/query/extraction/SearchQuerySpecDimExtractionFn.java @@ -19,6 +19,7 @@ package io.druid.query.extraction; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Preconditions; import io.druid.query.search.search.SearchQuerySpec; import java.nio.ByteBuffer; @@ -36,6 +37,8 @@ public class SearchQuerySpecDimExtractionFn extends DimExtractionFn @JsonProperty("query") SearchQuerySpec searchQuerySpec ) { + Preconditions.checkNotNull(searchQuerySpec, "search query must not be null"); + this.searchQuerySpec = searchQuerySpec; } @@ -74,4 +77,29 @@ public class SearchQuerySpecDimExtractionFn extends DimExtractionFn "searchQuerySpec=" + searchQuerySpec + '}'; } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SearchQuerySpecDimExtractionFn that = (SearchQuerySpecDimExtractionFn) o; + + if (!searchQuerySpec.equals(that.searchQuerySpec)) { + return false; + } + + return true; + } + + @Override + public int hashCode() + { + return searchQuerySpec.hashCode(); + } } diff --git a/processing/src/main/java/io/druid/query/extraction/TimeDimExtractionFn.java b/processing/src/main/java/io/druid/query/extraction/TimeDimExtractionFn.java index c11cca2f4e1..738a170c375 100644 --- a/processing/src/main/java/io/druid/query/extraction/TimeDimExtractionFn.java +++ b/processing/src/main/java/io/druid/query/extraction/TimeDimExtractionFn.java @@ -19,6 +19,7 @@ package io.druid.query.extraction; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Preconditions; import com.ibm.icu.text.SimpleDateFormat; import com.metamx.common.StringUtils; @@ -43,6 +44,9 @@ public class TimeDimExtractionFn extends DimExtractionFn @JsonProperty("resultFormat") String resultFormat ) { + Preconditions.checkNotNull(timeFormat, "timeFormat must not be null"); + Preconditions.checkNotNull(resultFormat, "resultFormat must not be null"); + this.timeFormat = timeFormat; this.timeFormatter = new SimpleDateFormat(timeFormat); this.timeFormatter.setLenient(true); @@ -100,4 +104,34 @@ public class TimeDimExtractionFn extends DimExtractionFn ", resultFormat='" + resultFormat + '\'' + '}'; } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TimeDimExtractionFn that = (TimeDimExtractionFn) o; + + if (!resultFormat.equals(that.resultFormat)) { + return false; + } + if (!timeFormat.equals(that.timeFormat)) { + return false; + } + + return true; + } + + @Override + public int hashCode() + { + int result = timeFormat.hashCode(); + result = 31 * result + resultFormat.hashCode(); + return result; + } } diff --git a/processing/src/main/java/io/druid/query/extraction/TimeFormatExtractionFn.java b/processing/src/main/java/io/druid/query/extraction/TimeFormatExtractionFn.java index 1c1a16fde92..043b7d05bd4 100644 --- a/processing/src/main/java/io/druid/query/extraction/TimeFormatExtractionFn.java +++ b/processing/src/main/java/io/druid/query/extraction/TimeFormatExtractionFn.java @@ -110,4 +110,38 @@ public class TimeFormatExtractionFn implements ExtractionFn { return false; } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TimeFormatExtractionFn that = (TimeFormatExtractionFn) o; + + if (locale != null ? !locale.equals(that.locale) : that.locale != null) { + return false; + } + if (!pattern.equals(that.pattern)) { + return false; + } + if (tz != null ? !tz.equals(that.tz) : that.tz != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() + { + int result = tz != null ? tz.hashCode() : 0; + result = 31 * result + pattern.hashCode(); + result = 31 * result + (locale != null ? locale.hashCode() : 0); + return result; + } } diff --git a/processing/src/test/java/io/druid/query/dimension/ExtractionDimensionSpecTest.java b/processing/src/test/java/io/druid/query/dimension/ExtractionDimensionSpecTest.java new file mode 100644 index 00000000000..d170e66ae28 --- /dev/null +++ b/processing/src/test/java/io/druid/query/dimension/ExtractionDimensionSpecTest.java @@ -0,0 +1,112 @@ +/* + * 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.dimension; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.druid.jackson.DefaultObjectMapper; +import io.druid.query.extraction.MatchingDimExtractionFn; +import io.druid.query.extraction.RegexDimExtractionFn; +import org.junit.Assert; +import org.junit.Test; + +public class ExtractionDimensionSpecTest +{ + @Test + public void testSerde() throws Exception + { + final ObjectMapper objectMapper = new DefaultObjectMapper(); + + final String oldJson = "{\n" + + " \"type\": \"extraction\",\n" + + " \"outputName\": \"first3Letters\",\n" + + " \"dimension\": \"myDim\"," + + " \"extractionFn\": {\n" + + " \"type\": \"regex\",\n" + + " \"expr\": \"(...).*\"\n" + + " }\n" + + "}"; + + final ExtractionDimensionSpec extractionDimensionSpec = (ExtractionDimensionSpec) objectMapper.readValue(oldJson, DimensionSpec.class); + + Assert.assertEquals("first3Letters", extractionDimensionSpec.getOutputName()); + Assert.assertEquals("myDim", extractionDimensionSpec.getDimension()); + Assert.assertNotNull(extractionDimensionSpec.getExtractionFn()); + Assert.assertTrue(extractionDimensionSpec.getExtractionFn() instanceof RegexDimExtractionFn); + + Assert.assertEquals( + extractionDimensionSpec, + objectMapper.readValue( + objectMapper.writeValueAsBytes(extractionDimensionSpec), + DimensionSpec.class + ) + ); + } + + @Test + public void testSerdeBackwardsCompatibility() throws Exception + { + final ObjectMapper objectMapper = new DefaultObjectMapper(); + + final String oldJson = "{\n" + + " \"type\": \"extraction\",\n" + + " \"outputName\": \"first3Letters\",\n" + + " \"dimension\": \"myDim\"," + + " \"dimExtractionFn\": {\n" + + " \"type\": \"regex\",\n" + + " \"expr\": \"(...).*\"\n" + + " }\n" + + "}"; + + final ExtractionDimensionSpec extractionDimensionSpec = (ExtractionDimensionSpec) objectMapper.readValue(oldJson, DimensionSpec.class); + + Assert.assertEquals("first3Letters", extractionDimensionSpec.getOutputName()); + Assert.assertEquals("myDim", extractionDimensionSpec.getDimension()); + Assert.assertNotNull(extractionDimensionSpec.getExtractionFn()); + Assert.assertTrue(extractionDimensionSpec.getExtractionFn() instanceof RegexDimExtractionFn); + + Assert.assertEquals( + extractionDimensionSpec, + objectMapper.readValue( + objectMapper.writeValueAsBytes(extractionDimensionSpec), + DimensionSpec.class + ) + ); + + // new trumps old + final String oldAndNewJson = "{\n" + + " \"type\": \"extraction\",\n" + + " \"outputName\": \"first3Letters\",\n" + + " \"dimension\": \"myDim\"," + + " \"extractionFn\": {\n" + + " \"type\": \"partial\",\n" + + " \"expr\": \"(...).*\"\n" + + " },\n" + + " \"dimExtractionFn\": {\n" + + " \"type\": \"regex\",\n" + + " \"expr\": \"(...).*\"\n" + + " }\n" + + "}"; + + Assert.assertTrue( + objectMapper.readValue(oldAndNewJson, DimensionSpec.class) + .getExtractionFn() instanceof MatchingDimExtractionFn + ); + } +} diff --git a/processing/src/test/java/io/druid/query/extraction/TimeFormatExtractionFnTest.java b/processing/src/test/java/io/druid/query/extraction/TimeFormatExtractionFnTest.java index 36b38a76bed..c1b5dccb2ad 100644 --- a/processing/src/test/java/io/druid/query/extraction/TimeFormatExtractionFnTest.java +++ b/processing/src/test/java/io/druid/query/extraction/TimeFormatExtractionFnTest.java @@ -87,12 +87,35 @@ public class TimeFormatExtractionFnTest } public void testSerde(TimeFormatExtractionFn fn, String format, DateTimeZone tz, String locale) throws Exception { - ObjectMapper objectMapper = new DefaultObjectMapper(); - String json = objectMapper.writeValueAsString(fn); + final ObjectMapper objectMapper = new DefaultObjectMapper(); + final String json = objectMapper.writeValueAsString(fn); TimeFormatExtractionFn deserialized = objectMapper.readValue(json, TimeFormatExtractionFn.class); Assert.assertEquals(format, deserialized.getFormat()); Assert.assertEquals(tz, deserialized.getTimeZone()); Assert.assertEquals(locale, deserialized.getLocale()); + + Assert.assertEquals(fn, deserialized); + } + + @Test + public void testSerdeFromJson() throws Exception + { + final ObjectMapper objectMapper = new DefaultObjectMapper(); + final String json = "{ \"type\" : \"timeFormat\", \"format\" : \"HH\" }"; + TimeFormatExtractionFn extractionFn = (TimeFormatExtractionFn) objectMapper.readValue(json, ExtractionFn.class); + + Assert.assertEquals("HH", extractionFn.getFormat()); + Assert.assertEquals(null, extractionFn.getLocale()); + Assert.assertEquals(null, extractionFn.getTimeZone()); + + // round trip + Assert.assertEquals( + extractionFn, + objectMapper.readValue( + objectMapper.writeValueAsBytes(extractionFn), + ExtractionFn.class + ) + ); } } diff --git a/processing/src/test/java/io/druid/query/extraction/extraction/JavascriptExtractionFnTest.java b/processing/src/test/java/io/druid/query/extraction/extraction/JavascriptExtractionFnTest.java index 7ad28dd8ea8..f6f4d783486 100644 --- a/processing/src/test/java/io/druid/query/extraction/extraction/JavascriptExtractionFnTest.java +++ b/processing/src/test/java/io/druid/query/extraction/extraction/JavascriptExtractionFnTest.java @@ -17,8 +17,10 @@ package io.druid.query.extraction.extraction; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; +import io.druid.jackson.DefaultObjectMapper; import io.druid.query.extraction.ExtractionFn; import io.druid.query.extraction.JavascriptExtractionFn; import org.joda.time.DateTime; @@ -307,4 +309,23 @@ public class JavascriptExtractionFnTest Assert.assertEquals(it.next(), res); } } + + @Test + public void testSerde() throws Exception + { + final ObjectMapper objectMapper = new DefaultObjectMapper(); + final String json = "{ \"type\" : \"javascript\", \"function\" : \"function(str) { return str.substring(0,3); }\" }"; + JavascriptExtractionFn extractionFn = (JavascriptExtractionFn) objectMapper.readValue(json, ExtractionFn.class); + + Assert.assertEquals("function(str) { return str.substring(0,3); }", extractionFn.getFunction()); + + // round trip + Assert.assertEquals( + extractionFn, + objectMapper.readValue( + objectMapper.writeValueAsBytes(extractionFn), + ExtractionFn.class + ) + ); + } } diff --git a/processing/src/test/java/io/druid/query/extraction/extraction/MatchingDimExtractionFnTest.java b/processing/src/test/java/io/druid/query/extraction/extraction/MatchingDimExtractionFnTest.java index 9706d044981..e8a5e330233 100644 --- a/processing/src/test/java/io/druid/query/extraction/extraction/MatchingDimExtractionFnTest.java +++ b/processing/src/test/java/io/druid/query/extraction/extraction/MatchingDimExtractionFnTest.java @@ -17,7 +17,9 @@ package io.druid.query.extraction.extraction; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Sets; +import io.druid.jackson.DefaultObjectMapper; import io.druid.query.extraction.ExtractionFn; import io.druid.query.extraction.MatchingDimExtractionFn; import org.junit.Assert; @@ -64,4 +66,23 @@ public class MatchingDimExtractionFnTest Assert.assertTrue(expected.contains(str)); } } + + @Test + public void testSerde() throws Exception + { + final ObjectMapper objectMapper = new DefaultObjectMapper(); + final String json = "{ \"type\" : \"partial\", \"expr\" : \".(...)?\" }"; + MatchingDimExtractionFn extractionFn = (MatchingDimExtractionFn) objectMapper.readValue(json, ExtractionFn.class); + + Assert.assertEquals(".(...)?", extractionFn.getExpr()); + + // round trip + Assert.assertEquals( + extractionFn, + objectMapper.readValue( + objectMapper.writeValueAsBytes(extractionFn), + ExtractionFn.class + ) + ); + } } diff --git a/processing/src/test/java/io/druid/query/extraction/extraction/RegexDimExtractionFnTest.java b/processing/src/test/java/io/druid/query/extraction/extraction/RegexDimExtractionFnTest.java index 4746ee33a60..ca4151d7003 100644 --- a/processing/src/test/java/io/druid/query/extraction/extraction/RegexDimExtractionFnTest.java +++ b/processing/src/test/java/io/druid/query/extraction/extraction/RegexDimExtractionFnTest.java @@ -17,7 +17,9 @@ package io.druid.query.extraction.extraction; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Sets; +import io.druid.jackson.DefaultObjectMapper; import io.druid.query.extraction.ExtractionFn; import io.druid.query.extraction.RegexDimExtractionFn; import org.junit.Assert; @@ -99,4 +101,23 @@ public class RegexDimExtractionFnTest Assert.assertTrue(extracted.contains("b")); Assert.assertTrue(extracted.contains("c")); } + + @Test + public void testSerde() throws Exception + { + final ObjectMapper objectMapper = new DefaultObjectMapper(); + final String json = "{ \"type\" : \"regex\", \"expr\" : \".(...)?\" }"; + RegexDimExtractionFn extractionFn = (RegexDimExtractionFn) objectMapper.readValue(json, ExtractionFn.class); + + Assert.assertEquals(".(...)?", extractionFn.getExpr()); + + // round trip + Assert.assertEquals( + extractionFn, + objectMapper.readValue( + objectMapper.writeValueAsBytes(extractionFn), + ExtractionFn.class + ) + ); + } } diff --git a/processing/src/test/java/io/druid/query/extraction/extraction/TimeDimExtractionFnTest.java b/processing/src/test/java/io/druid/query/extraction/extraction/TimeDimExtractionFnTest.java index d9f327d46c0..dd8da43c370 100644 --- a/processing/src/test/java/io/druid/query/extraction/extraction/TimeDimExtractionFnTest.java +++ b/processing/src/test/java/io/druid/query/extraction/extraction/TimeDimExtractionFnTest.java @@ -17,8 +17,11 @@ package io.druid.query.extraction.extraction; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Sets; +import io.druid.jackson.DefaultObjectMapper; import io.druid.query.extraction.ExtractionFn; +import io.druid.query.extraction.MatchingDimExtractionFn; import io.druid.query.extraction.TimeDimExtractionFn; import org.junit.Assert; import org.junit.Test; @@ -70,4 +73,24 @@ public class TimeDimExtractionFnTest Assert.assertTrue(quarters.contains("Q2/2012")); Assert.assertTrue(quarters.contains("Q4/2012")); } + + @Test + public void testSerde() throws Exception + { + final ObjectMapper objectMapper = new DefaultObjectMapper(); + final String json = "{ \"type\" : \"time\", \"timeFormat\" : \"MM/dd/yyyy\", \"resultFormat\" : \"QQQ/yyyy\" }"; + TimeDimExtractionFn extractionFn = (TimeDimExtractionFn) objectMapper.readValue(json, ExtractionFn.class); + + Assert.assertEquals("MM/dd/yyyy", extractionFn.getTimeFormat()); + Assert.assertEquals("QQQ/yyyy", extractionFn.getResultFormat()); + + // round trip + Assert.assertEquals( + extractionFn, + objectMapper.readValue( + objectMapper.writeValueAsBytes(extractionFn), + ExtractionFn.class + ) + ); + } }