diff --git a/pom.xml b/pom.xml
index 3c227f4..fad61bc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,6 +24,7 @@
parserlinter
+ rewriter
diff --git a/rewriter/pom.xml b/rewriter/pom.xml
new file mode 100644
index 0000000..879629b
--- /dev/null
+++ b/rewriter/pom.xml
@@ -0,0 +1,175 @@
+
+
+ 4.0.0
+
+
+ com.facebook.presto
+ presto-coresql
+ 0.2-SNAPSHOT
+
+
+ presto-coresql-rewriter
+ presto-coresql-rewriter
+
+
+ ${project.parent.basedir}
+ 1.6
+ 1.6
+
+
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+
+ org.testng
+ testng
+ test
+
+
+
+ com.facebook.presto
+ presto-coresql-parser
+ 0.2-SNAPSHOT
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ generate-sources
+
+ add-source
+
+
+
+
+
+
+
+
+
+
+
+ com.facebook.presto
+ presto-maven-plugin
+ 0.3
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.1.1
+
+
+
+ org.skife.maven
+ really-executable-jar-maven-plugin
+ 1.0.5
+
+
+
+ org.apache.maven.plugins
+ maven-antrun-plugin
+ 1.8
+
+
+
+ io.airlift.maven.plugins
+ sphinx-maven-plugin
+ 2.1
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+
+
+
+
+
+ org.alluxio:alluxio-shaded-client
+ org.codehaus.plexus:plexus-utils
+ com.google.guava:guava
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-release-plugin
+
+ clean verify -DskipTests
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ true
+
+ -verbose
+ -J-Xss100M
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ **/*.java
+ target/**/*.java
+ **/Benchmark*.java
+
+
+ **/*jmhTest*.java
+ **/*jmhType*.java
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+ true
+
+
+ true
+ true
+ false
+
+
+
+
+
+
+
+
+ org.gaul
+ modernizer-maven-plugin
+ 2.1.0
+
+ 1.8
+
+
+
+
+
+
diff --git a/rewriter/src/main/java/com/facebook/coresql/rewriter/PatternMatcher.java b/rewriter/src/main/java/com/facebook/coresql/rewriter/PatternMatcher.java
new file mode 100644
index 0000000..a4a4c35
--- /dev/null
+++ b/rewriter/src/main/java/com/facebook/coresql/rewriter/PatternMatcher.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed 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 com.facebook.coresql.rewriter;
+
+import com.facebook.coresql.parser.AstNode;
+
+import java.util.Set;
+
+public interface PatternMatcher
+{
+ /**
+ * Traverses an AST, finding the set of nodes that match this pattern matcher's pattern
+ *
+ * @param node root of the AST
+ * @return The set of nodes that match this pattern matcher's pattern
+ */
+ Set findMatchedNodes(AstNode node);
+}
diff --git a/rewriter/src/main/java/com/facebook/coresql/rewriter/RewriteResult.java b/rewriter/src/main/java/com/facebook/coresql/rewriter/RewriteResult.java
new file mode 100644
index 0000000..7bef822
--- /dev/null
+++ b/rewriter/src/main/java/com/facebook/coresql/rewriter/RewriteResult.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed 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 com.facebook.coresql.rewriter;
+
+import static java.util.Objects.requireNonNull;
+
+public class RewriteResult
+{
+ private String nameOfRewrite;
+ private String originalSql;
+ private String rewrittenSql;
+
+ public RewriteResult(String nameOfRewrite, String originalSql, String rewrittenSql)
+ {
+ this.nameOfRewrite = requireNonNull(nameOfRewrite, "name of rewrite is null");
+ this.originalSql = requireNonNull(originalSql, "original sql statement is null");
+ this.rewrittenSql = requireNonNull(rewrittenSql, "rewritten sql statement is null");
+ }
+
+ public String getNameOfRewrite()
+ {
+ return nameOfRewrite;
+ }
+
+ public String getOriginalSql()
+ {
+ return originalSql;
+ }
+
+ public String getRewrittenSql()
+ {
+ return rewrittenSql;
+ }
+}
diff --git a/rewriter/src/main/java/com/facebook/coresql/rewriter/Rewriter.java b/rewriter/src/main/java/com/facebook/coresql/rewriter/Rewriter.java
new file mode 100644
index 0000000..e40a7b2
--- /dev/null
+++ b/rewriter/src/main/java/com/facebook/coresql/rewriter/Rewriter.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed 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 com.facebook.coresql.rewriter;
+
+import com.facebook.coresql.parser.Unparser;
+
+public abstract class Rewriter
+ extends Unparser
+{
+ public abstract RewriteResult rewrite(String originalSql);
+}
diff --git a/rewriter/src/main/java/com/facebook/coresql/rewriter/approxpercentile/ApproxPercentilePatternMatcher.java b/rewriter/src/main/java/com/facebook/coresql/rewriter/approxpercentile/ApproxPercentilePatternMatcher.java
new file mode 100644
index 0000000..32246c9
--- /dev/null
+++ b/rewriter/src/main/java/com/facebook/coresql/rewriter/approxpercentile/ApproxPercentilePatternMatcher.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed 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 com.facebook.coresql.rewriter.approxpercentile;
+
+import com.facebook.coresql.parser.AstNode;
+import com.facebook.coresql.parser.Select;
+import com.facebook.coresql.parser.SqlParserDefaultVisitor;
+import com.facebook.coresql.rewriter.PatternMatcher;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.facebook.coresql.parser.SqlParserTreeConstants.JJTARGUMENTLIST;
+import static com.facebook.coresql.parser.SqlParserTreeConstants.JJTFUNCTIONCALL;
+import static com.facebook.coresql.parser.SqlParserTreeConstants.JJTIDENTIFIER;
+import static com.facebook.coresql.parser.SqlParserTreeConstants.JJTSELECTLIST;
+import static com.facebook.coresql.parser.SqlParserTreeConstants.JJTUNSIGNEDNUMERICLITERAL;
+import static java.util.Objects.requireNonNull;
+
+public class ApproxPercentilePatternMatcher
+ extends SqlParserDefaultVisitor
+ implements PatternMatcher
+{
+ private final Map>> selectMap; // A map of SelectNode to a map of each unique first arg that has duplicate APPROX_PERCENTILE nodes
+ private boolean nonLiteralSecondArgFound;
+
+ public ApproxPercentilePatternMatcher()
+ {
+ selectMap = new HashMap<>();
+ }
+
+ private boolean isLiteral(AstNode node)
+ {
+ return node.getId() == JJTUNSIGNEDNUMERICLITERAL;
+ }
+
+ private void processSelectItem(AstNode node, Map> map)
+ {
+ AstNode functionCall = node.GetFirstChildOfKind(JJTFUNCTIONCALL);
+ if (functionCall == null) {
+ return;
+ }
+ AstNode identifier = functionCall.GetFirstChildOfKind(JJTIDENTIFIER);
+ if (identifier == null || !identifier.GetImage().equalsIgnoreCase("APPROX_PERCENTILE")) {
+ return;
+ }
+ AstNode argList = functionCall.GetFirstChildOfKind(JJTARGUMENTLIST);
+ AstNode firstArg = (AstNode) argList.jjtGetChild(0);
+ AstNode secondArg = (AstNode) argList.jjtGetChild(1);
+ if (!isLiteral(secondArg)) {
+ nonLiteralSecondArgFound = true;
+ return;
+ }
+ map.putIfAbsent(firstArg.GetImage(), new LinkedList<>());
+ map.get(firstArg.GetImage()).add(node);
+ }
+
+ private boolean hasMultipleApproxPercentilesWithSameArgs(Map> map)
+ {
+ if (nonLiteralSecondArgFound) {
+ nonLiteralSecondArgFound = false;
+ return false;
+ }
+ return map.keySet().size() == 1 && map.get(map.keySet().iterator().next()).size() > 1;
+ }
+
+ private Map> processSelectList(AstNode node)
+ {
+ Map> map = new HashMap<>();
+ for (int i = 0; i < node.NumChildren(); i++) {
+ processSelectItem(node.GetChild(i), map);
+ }
+ if (hasMultipleApproxPercentilesWithSameArgs(map)) {
+ return map;
+ }
+ return null;
+ }
+
+ public void processSelectNode(AstNode node)
+ {
+ AstNode selectList = node.GetFirstChildOfKind(JJTSELECTLIST);
+ Map> map = processSelectList(selectList);
+ if (map != null) {
+ selectMap.put(node, map);
+ }
+ }
+
+ @Override
+ public void visit(Select node, Void data)
+ {
+ processSelectNode(node);
+ defaultVisit(node, data);
+ }
+
+ @Override
+ public Set findMatchedNodes(AstNode node)
+ {
+ return null;
+ }
+
+ public Map>> getSelectMap(AstNode root)
+ {
+ requireNonNull(root, "AST passed to pattern matcher was null");
+ root.jjtAccept(this, null);
+ return selectMap;
+ }
+}
diff --git a/rewriter/src/main/java/com/facebook/coresql/rewriter/approxpercentile/ApproxPercentileRewriter.java b/rewriter/src/main/java/com/facebook/coresql/rewriter/approxpercentile/ApproxPercentileRewriter.java
new file mode 100644
index 0000000..418bdec
--- /dev/null
+++ b/rewriter/src/main/java/com/facebook/coresql/rewriter/approxpercentile/ApproxPercentileRewriter.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed 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 com.facebook.coresql.rewriter.approxpercentile;
+
+import com.facebook.coresql.parser.AstNode;
+import com.facebook.coresql.parser.Select;
+import com.facebook.coresql.parser.Unparser;
+import com.facebook.coresql.rewriter.RewriteResult;
+import com.facebook.coresql.rewriter.Rewriter;
+
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import static com.facebook.coresql.parser.ParserHelper.parseStatement;
+import static com.facebook.coresql.parser.SqlParserTreeConstants.JJTALIAS;
+import static com.facebook.coresql.parser.SqlParserTreeConstants.JJTARGUMENTLIST;
+import static com.facebook.coresql.parser.SqlParserTreeConstants.JJTFROMCLAUSE;
+import static com.facebook.coresql.parser.SqlParserTreeConstants.JJTFUNCTIONCALL;
+import static com.facebook.coresql.parser.SqlParserTreeConstants.JJTIDENTIFIER;
+import static com.facebook.coresql.parser.SqlParserTreeConstants.JJTSELECTLIST;
+import static com.facebook.coresql.parser.SqlParserTreeConstants.JJTTABLEEXPRESSION;
+import static com.facebook.coresql.parser.SqlParserTreeConstants.JJTTABLENAME;
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+public class ApproxPercentileRewriter
+ extends Rewriter
+{
+ private static final String REPLACEMENT = "SELECT %s FROM (SELECT %sAPPROX_PERCENTILE(%s, ARRAY[%s]) AS percentiles FROM %s)";
+ private Map>> selectMap; // A map of SelectNode to a map of each unique first arg that has duplicate APPROX_PERCENTILEs
+ private final List nonRewrittenSelectListItems; // Items in the select list that should not be rewritten
+ private static final String REWRITE_NAME = "Multiple APPROX PERCENTILE with same first arg and literal second arg";
+
+ public ApproxPercentileRewriter()
+ {
+ this.nonRewrittenSelectListItems = new LinkedList<>();
+ this.selectMap = new HashMap<>();
+ }
+
+ private String getRewrittenSelectList(List approxPercentiles, AstNode selectList)
+ {
+ StringBuilder sb = new StringBuilder();
+ int approxPercentileIdx = 1;
+ int nonRewrittenAliasNum = 1;
+ for (int i = 0; i < selectList.jjtGetNumChildren(); i++) {
+ AstNode selectListItem = selectList.GetChild(i);
+ if (approxPercentiles.contains(selectListItem)) {
+ sb.append(format("percentiles[%d]", approxPercentileIdx));
+ AstNode aliasNode = selectListItem.GetFirstChildOfKind(JJTALIAS);
+ if (aliasNode != null) {
+ sb.append(format(" AS %s", aliasNode.GetFirstChildOfKind(JJTIDENTIFIER).GetImage()));
+ }
+ approxPercentileIdx++;
+ }
+ else {
+ sb.append(format("_alias%d", nonRewrittenAliasNum));
+ nonRewrittenSelectListItems.add(selectListItem);
+ nonRewrittenAliasNum++;
+ }
+ sb.append(", ");
+ }
+ sb.replace(sb.length() - 2, sb.length(), ""); // Remove trailing comma and whitespace
+ return sb.toString().trim();
+ }
+
+ private String getAliasesForNonRewrittens()
+ {
+ StringBuilder sb = new StringBuilder();
+ int nonRewrittenAliasNum = 1;
+ for (AstNode selectItem : nonRewrittenSelectListItems) {
+ sb.append(format("%s AS _alias%d", Unparser.unparse(selectItem).trim(), nonRewrittenAliasNum));
+ sb.append(", ");
+ nonRewrittenAliasNum++;
+ }
+ return sb.toString();
+ }
+
+ private String getRewrittenApproxPercentileClause(List approxPercentiles)
+ {
+ StringBuilder sb = new StringBuilder();
+ for (AstNode selectItem : approxPercentiles) {
+ AstNode secondArg = getSecondArg(selectItem);
+ sb.append(secondArg.GetImage());
+ sb.append(", ");
+ }
+ sb.replace(sb.length() - 2, sb.length(), ""); // Remove trailing comma and whitespace
+ return sb.toString().trim();
+ }
+
+ private AstNode getSecondArg(AstNode selectItem)
+ {
+ AstNode functionCall = selectItem.GetFirstChildOfKind(JJTFUNCTIONCALL);
+ AstNode argList = functionCall.GetFirstChildOfKind(JJTARGUMENTLIST);
+ return (AstNode) argList.jjtGetChild(1);
+ }
+
+ /**
+ * Generates a rewritten version of the SELECT clause given, places that version in the Unparser, then skips the original SELECT.
+ *
+ * @param select The SELECT node we're rewriting
+ */
+ private void applyRewriting(AstNode select)
+ {
+ String tableName = select.GetFirstChildOfKind(JJTTABLEEXPRESSION).GetFirstChildOfKind(JJTFROMCLAUSE).GetFirstChildOfKind(JJTTABLENAME).GetFirstChildOfKind(JJTIDENTIFIER).GetImage();
+ Map> map = selectMap.get(select); // >
+ String firstArg = map.keySet().iterator().next();
+ List approxPercentileNodes = map.get(firstArg);
+ AstNode selectList = select.GetFirstChildOfKind(JJTSELECTLIST);
+
+ Formatter formatter = new Formatter(stringBuilder);
+ formatter.format(REPLACEMENT, getRewrittenSelectList(approxPercentileNodes, selectList), getAliasesForNonRewrittens(), firstArg, getRewrittenApproxPercentileClause(approxPercentileNodes), tableName);
+
+ // Move to end of this SELECT node -- we've already put in a rewritten version of it
+ unparseUpto(select);
+ moveToEndOfNode(select);
+ }
+
+ @Override
+ public void visit(Select node, Void data)
+ {
+ if (selectMap.containsKey(node)) {
+ applyRewriting(node);
+ }
+ else {
+ defaultVisit(node, data);
+ }
+ }
+
+ @Override
+ public RewriteResult rewrite(String originalSql)
+ {
+ AstNode sqlAsAst = requireNonNull(parseStatement(originalSql));
+ this.selectMap = new ApproxPercentilePatternMatcher().getSelectMap(sqlAsAst);
+ String rewrittenSql = Unparser.unparse(sqlAsAst, this);
+ return new RewriteResult(REWRITE_NAME, originalSql, rewrittenSql);
+ }
+}
diff --git a/rewriter/src/test/java/com/facebook/coresql/rewriter/TestApproxPercentileRewriter.java b/rewriter/src/test/java/com/facebook/coresql/rewriter/TestApproxPercentileRewriter.java
new file mode 100644
index 0000000..a965f37
--- /dev/null
+++ b/rewriter/src/test/java/com/facebook/coresql/rewriter/TestApproxPercentileRewriter.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed 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 com.facebook.coresql.rewriter;
+
+import com.facebook.coresql.parser.AstNode;
+import com.facebook.coresql.rewriter.approxpercentile.ApproxPercentilePatternMatcher;
+import com.facebook.coresql.rewriter.approxpercentile.ApproxPercentileRewriter;
+import org.testng.annotations.Test;
+
+import static com.facebook.coresql.parser.ParserHelper.parseStatement;
+import static java.lang.String.format;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+public class TestApproxPercentileRewriter
+{
+ private static final String[] statementsThatDontNeedAnyRewrite = new String[] {
+ // False Positive
+ "CREATE TABLE blah AS SELECT * FROM (SELECT * FROM (SELECT foo FROM T ORDER BY x LIMIT 10) ORDER BY y LIMIT 10) ORDER BY z LIMIT 10;",
+ "SELECT dealer_id, sales OVER (PARTITION BY dealer_id ORDER BY sales);",
+ "INSERT INTO blah SELECT * FROM (SELECT t.date, t.code, t.qty FROM sales AS t ORDER BY t.date LIMIT 100);",
+ "SELECT (true or false) and false;",
+ // True Negative
+ "SELECT * FROM T ORDER BY y;",
+ "SELECT * FROM T ORDER BY y LIMIT 10;",
+ "use a.b;",
+ " SELECT 1;",
+ "SELECT a FROM T;",
+ "SELECT a FROM T WHERE p1 > p2;",
+ "SELECT a, b, c FROM T WHERE c1 < c2 and c3 < c4;",
+ "SELECT CASE a WHEN IN ( 1 ) THEN b ELSE c END AS x, b, c FROM T WHERE c1 < c2 and c3 < c4;",
+ "SELECT T.* FROM T JOIN W ON T.x = W.x;",
+ "SELECT NULL;",
+ "SELECT ARRAY[x] FROM T;",
+ "SELECT TRANSFORM(ARRAY[x], x -> x + 2) AS arra FROM T;",
+ "CREATE TABLE T AS SELECT TRANSFORM(ARRAY[x], x -> x + 2) AS arra FROM T;",
+ "INSERT INTO T SELECT TRANSFORM(ARRAY[x], x -> x + 2) AS arra FROM T;",
+ "SELECT ROW_NUMBER() OVER(PARTITION BY x) FROM T;",
+ "SELECT x, SUM(y) OVER (PARTITION BY y ORDER BY 1) AS min\n" +
+ "FROM (values ('b',10), ('a', 10)) AS T(x, y)\n;",
+ "SELECT\n" +
+ " CAST(MAP() AS map>) AS \"bool_tensor_features\";",
+ "SELECT f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f())))))))))))))))))))))))))))));",
+ "SELECT abs, 2 as abs;",
+ };
+
+ private static final String[] statementsThatNeedApproxPercentileRewrite = new String[] {
+ // True Positive
+ "SELECT APPROX_PERCENTILE(x, 0.1) AS percentile_10, APPROX_PERCENTILE(x, 0.2) AS percentile_20, APPROX_PERCENTILE(x, 0.3) AS percentile_30 FROM T;",
+ "SELECT APPROX_PERCENTILE(x, 0.1), APPROX_PERCENTILE(x, 0.2) AS percentile_20, APPROX_PERCENTILE(x, 0.3) FROM T;",
+ "SELECT approx_percentile(y, 0.2), x + 1, approx_percentile(y, 0.1) from T group by 2;",
+ // Subquery
+ "SELECT x FROM (SELECT APPROX_PERCENTILE(x, 0.1) AS percentile_10, APPROX_PERCENTILE(x, 0.2) AS percentile_20, APPROX_PERCENTILE(x, 0.3) AS percentile_30 FROM T);",
+ "CREATE TABLE blah AS SELECT * FROM (SELECT * FROM (SELECT approx_percentile(y, 0.2), x + 1, approx_percentile(y, 0.1) from T group by 2));"
+ };
+
+ private static final String[] statementsThatDontNeedApproxPercentileRewrite = new String[] {
+ // False Positive
+ "SELECT APPROX_PERCENTILE(x, 0.1) AS percentile_10, APPROX_PERCENTILE(y, 0.2) AS percentile_20, APPROX_PERCENTILE(z, 0.3) AS percentile_30 FROM T;",
+ "SELECT APPROX_PERCENTILE(x, 0.1) AS percentile_10, APPROX_PERCENTILE(x, 0.2) AS percentile_20, APPROX_PERCENTILE(z, 0.3) AS percentile_30 FROM T;",
+ "SELECT APPROX_PERCENTILE(x, ARRAY[0.1, 0.2, 0.3]), APPROX_PERCENTILE(x, 0.4), APPROX_PERCENTILE(x, 0.5) FROM T;",
+ "SELECT APPROX_PERCENTILE(x, ARRAY[0.1, 0.2, 0.3]) FROM (SELECT APPROX_PERCENTILE(x, 0.1) from T);",
+ "SELECT APPROX_PERCENTILE(x, 0.1) FROM (SELECT APPROX_PERCENTILE(x, 0.2) FROM T);",
+ "SELECT APPROX_PERCENTILE(x, 0.1) FROM (SELECT * FROM (SELECT approx_percentile(x, 0.1), approx_percentile(y, 0.1) from T group by 2));"
+ };
+
+ private void assertApproxPercentilePatternIsMatchedOrUnmatched(String sql, boolean isMatched)
+ {
+ AstNode ast = parseStatement(sql);
+ assertNotNull(ast);
+ if (isMatched) {
+ assertTrue(new ApproxPercentilePatternMatcher().getSelectMap(ast).size() >= 1);
+ }
+ else {
+ assertEquals(new ApproxPercentilePatternMatcher().getSelectMap(ast).size(), 0);
+ }
+ }
+
+ private void rewriteThenPrint(String sql)
+ {
+ String rewritten = new ApproxPercentileRewriter().rewrite(sql).getRewrittenSql();
+ System.out.println(format("Before --> %s", sql));
+ System.out.println(format("AFTER --> %s", rewritten));
+ System.out.println();
+ }
+
+ @Test
+ public void approxPercentilePatternDetectionTest()
+ {
+ for (String sql : statementsThatNeedApproxPercentileRewrite) {
+ assertApproxPercentilePatternIsMatchedOrUnmatched(sql, true);
+ rewriteThenPrint(sql);
+ }
+
+ for (String sql : statementsThatDontNeedApproxPercentileRewrite) {
+ assertApproxPercentilePatternIsMatchedOrUnmatched(sql, false);
+ }
+
+ for (String sql : statementsThatDontNeedAnyRewrite) {
+ assertApproxPercentilePatternIsMatchedOrUnmatched(sql, false);
+ }
+ }
+}
diff --git a/rewriter/~$pom.xml b/rewriter/~$pom.xml
new file mode 100644
index 0000000..0499b88
Binary files /dev/null and b/rewriter/~$pom.xml differ