From def823124c642586a7471a7b12dacd99b905628b Mon Sep 17 00:00:00 2001 From: dongyifeng Date: Wed, 9 Jan 2019 09:17:03 +0800 Subject: [PATCH] add version comparator for StringComparator (#6745) * add version comparator for StringComparator * add more test case and docs --- docs/content/querying/filters.md | 2 +- docs/content/querying/sorting-orders.md | 5 ++ pom.xml | 5 ++ processing/pom.xml | 5 ++ .../druid/jackson/StringComparatorModule.java | 3 +- .../query/ordering/StringComparator.java | 2 + .../query/ordering/StringComparators.java | 51 +++++++++++++++++++ .../query/ordering/StringComparatorsTest.java | 14 +++++ 8 files changed, 85 insertions(+), 2 deletions(-) diff --git a/docs/content/querying/filters.md b/docs/content/querying/filters.md index 23d733eda7d..e1269811ee0 100644 --- a/docs/content/querying/filters.md +++ b/docs/content/querying/filters.md @@ -276,7 +276,7 @@ greater than, less than, greater than or equal to, less than or equal to, and "b |upper|String|The upper bound for the filter|no| |lowerStrict|Boolean|Perform strict comparison on the lower bound (">" instead of ">=")|no, default: false| |upperStrict|Boolean|Perform strict comparison on the upper bound ("<" instead of "<=")|no, default: false| -|ordering|String|Specifies the sorting order to use when comparing values against the bound. Can be one of the following values: "lexicographic", "alphanumeric", "numeric", "strlen". See [Sorting Orders](./sorting-orders.html) for more details.|no, default: "lexicographic"| +|ordering|String|Specifies the sorting order to use when comparing values against the bound. Can be one of the following values: "lexicographic", "alphanumeric", "numeric", "strlen", "version". See [Sorting Orders](./sorting-orders.html) for more details.|no, default: "lexicographic"| |extractionFn|[Extraction function](#filtering-with-extraction-functions)| Extraction function to apply to the dimension|no| Bound filters support the use of extraction functions, see [Filtering with Extraction Functions](#filtering-with-extraction-functions) for details. diff --git a/docs/content/querying/sorting-orders.md b/docs/content/querying/sorting-orders.md index 2604b524d62..5c83ac467dc 100644 --- a/docs/content/querying/sorting-orders.md +++ b/docs/content/querying/sorting-orders.md @@ -47,3 +47,8 @@ When comparing two unparseable values (e.g., "hello" and "world"), this ordering ## Strlen Sorts values by the their string lengths. When there is a tie, this comparator falls back to using the String compareTo method. + +## Version +Sorts values as versions, e.g.: "10.0 sorts after 9.0", "1.0.0-SNAPSHOT sorts after 1.0.0". + +See https://maven.apache.org/ref/3.6.0/maven-artifact/apidocs/org/apache/maven/artifact/versioning/ComparableVersion.html for more details on how this ordering sorts values. \ No newline at end of file diff --git a/pom.xml b/pom.xml index d9c1387e50f..03bf81453c2 100644 --- a/pom.xml +++ b/pom.xml @@ -749,6 +749,11 @@ caffeine ${caffeine.version} + + org.apache.maven + maven-artifact + 3.6.0 + org.apache.calcite diff --git a/processing/pom.xml b/processing/pom.xml index 542c3f22f60..714a1f2a025 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -116,6 +116,11 @@ checker ${checkerframework.version} + + org.apache.maven + maven-artifact + + diff --git a/processing/src/main/java/org/apache/druid/jackson/StringComparatorModule.java b/processing/src/main/java/org/apache/druid/jackson/StringComparatorModule.java index a072ba19199..4b7ea2953db 100644 --- a/processing/src/main/java/org/apache/druid/jackson/StringComparatorModule.java +++ b/processing/src/main/java/org/apache/druid/jackson/StringComparatorModule.java @@ -36,7 +36,8 @@ public class StringComparatorModule extends SimpleModule new NamedType(StringComparators.LexicographicComparator.class, StringComparators.LEXICOGRAPHIC_NAME), new NamedType(StringComparators.AlphanumericComparator.class, StringComparators.ALPHANUMERIC_NAME), new NamedType(StringComparators.StrlenComparator.class, StringComparators.STRLEN_NAME), - new NamedType(StringComparators.NumericComparator.class, StringComparators.NUMERIC_NAME) + new NamedType(StringComparators.NumericComparator.class, StringComparators.NUMERIC_NAME), + new NamedType(StringComparators.VersionComparator.class, StringComparators.VERSION_NAME) ); } diff --git a/processing/src/main/java/org/apache/druid/query/ordering/StringComparator.java b/processing/src/main/java/org/apache/druid/query/ordering/StringComparator.java index 33016c4d2af..8e66baf7df3 100644 --- a/processing/src/main/java/org/apache/druid/query/ordering/StringComparator.java +++ b/processing/src/main/java/org/apache/druid/query/ordering/StringComparator.java @@ -39,6 +39,8 @@ public abstract class StringComparator implements Comparator return StringComparators.STRLEN; case StringComparators.NUMERIC_NAME: return StringComparators.NUMERIC; + case StringComparators.VERSION_NAME: + return StringComparators.VERSION; default: throw new IAE("Unknown string comparator[%s]", type); } diff --git a/processing/src/main/java/org/apache/druid/query/ordering/StringComparators.java b/processing/src/main/java/org/apache/druid/query/ordering/StringComparators.java index dae5cdac2bd..879e5a449f9 100644 --- a/processing/src/main/java/org/apache/druid/query/ordering/StringComparators.java +++ b/processing/src/main/java/org/apache/druid/query/ordering/StringComparators.java @@ -24,6 +24,7 @@ import com.google.common.primitives.Ints; import com.google.common.primitives.UnsignedBytes; import org.apache.druid.common.guava.GuavaUtils; import org.apache.druid.java.util.common.StringUtils; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import java.math.BigDecimal; import java.util.Comparator; @@ -34,16 +35,19 @@ public class StringComparators public static final String ALPHANUMERIC_NAME = "alphanumeric"; public static final String NUMERIC_NAME = "numeric"; public static final String STRLEN_NAME = "strlen"; + public static final String VERSION_NAME = "version"; public static final StringComparator LEXICOGRAPHIC = new LexicographicComparator(); public static final StringComparator ALPHANUMERIC = new AlphanumericComparator(); public static final StringComparator NUMERIC = new NumericComparator(); public static final StringComparator STRLEN = new StrlenComparator(); + public static final StringComparator VERSION = new VersionComparator(); public static final int LEXICOGRAPHIC_CACHE_ID = 0x01; public static final int ALPHANUMERIC_CACHE_ID = 0x02; public static final int NUMERIC_CACHE_ID = 0x03; public static final int STRLEN_CACHE_ID = 0x04; + public static final int VERSION_CACHE_ID = 0x05; public static class LexicographicComparator extends StringComparator { @@ -416,4 +420,51 @@ public class StringComparators return new byte[]{(byte) NUMERIC_CACHE_ID}; } } + + public static class VersionComparator extends StringComparator + { + @Override + public int compare(String o1, String o2) + { + //noinspection StringEquality + if (o1 == o2) { + return 0; + } + if (o1 == null) { + return -1; + } + if (o2 == null) { + return 1; + } + + DefaultArtifactVersion version1 = new DefaultArtifactVersion(o1); + DefaultArtifactVersion version2 = new DefaultArtifactVersion(o2); + return version1.compareTo(version2); + } + + @Override + public String toString() + { + return StringComparators.VERSION_NAME; + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + return true; + } + + @Override + public byte[] getCacheKey() + { + return new byte[]{(byte) VERSION_CACHE_ID}; + } + } } diff --git a/processing/src/test/java/org/apache/druid/query/ordering/StringComparatorsTest.java b/processing/src/test/java/org/apache/druid/query/ordering/StringComparatorsTest.java index 3b48420c824..7fa0a3b82ca 100644 --- a/processing/src/test/java/org/apache/druid/query/ordering/StringComparatorsTest.java +++ b/processing/src/test/java/org/apache/druid/query/ordering/StringComparatorsTest.java @@ -142,6 +142,20 @@ public class StringComparatorsTest Assert.assertTrue(StringComparators.NUMERIC.compare("CAN'T PARSE THIS", "CAN'T TOUCH THIS") < 0); } + @Test + public void testVersionComparator() + { + commonTest(StringComparators.VERSION); + + Assert.assertTrue(StringComparators.VERSION.compare("02", "002") == 0); + Assert.assertTrue(StringComparators.VERSION.compare("1.0", "2.0") < 0); + Assert.assertTrue(StringComparators.VERSION.compare("9.1", "10.0") < 0); + Assert.assertTrue(StringComparators.VERSION.compare("1.1.1", "2.0") < 0); + Assert.assertTrue(StringComparators.VERSION.compare("1.0-SNAPSHOT", "1.0") < 0); + Assert.assertTrue(StringComparators.VERSION.compare("2.0.1-xyz-1", "2.0.1-1-xyz") < 0); + Assert.assertTrue(StringComparators.VERSION.compare("1.0-SNAPSHOT", "1.0-Final") < 0); + } + @Test public void testLexicographicComparatorSerdeTest() throws IOException {