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
{