[NO ISSUE][COMP] Index selection hints

- user model changes: no
- storage format changes: no
- interface changes: no

Details:
- Introduce use-index hint that forces optimizer to choose specified
  indexes: /* +use-index(index_name_1, ... index_name_N) */
- Extend skip-index hint to accept a list of index names that
  must be excluded: /* +skip-index(index_name_1, ... index_name_N) */
- Extend indexnl hint to accept a list of index names that
  must be considered: /* +indexnl(index_name_1, ... index_name_N) */
- Added testcases

Change-Id: I7f124dbc2c03e7ec863058b2df6e59b11c43bad4
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/9124
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Dmitry Lychagin <dmitry.lychagin@couchbase.com>
Reviewed-by: Ali Alsuliman <ali.al.solaiman@gmail.com>
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
index a4995da..645fc27 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
+++ b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
@@ -56,10 +56,12 @@
 import org.apache.asterix.common.annotations.FieldValFileDataGen;
 import org.apache.asterix.common.annotations.FieldValFileSameIndexDataGen;
 import org.apache.asterix.common.annotations.IRecordFieldDataGen;
+import org.apache.asterix.common.annotations.IndexedNLJoinExpressionAnnotation;
 import org.apache.asterix.common.annotations.InsertRandIntDataGen;
 import org.apache.asterix.common.annotations.ListDataGen;
 import org.apache.asterix.common.annotations.ListValFileDataGen;
 import org.apache.asterix.common.annotations.RangeAnnotation;
+import org.apache.asterix.common.annotations.SecondaryIndexSearchPreferenceAnnotation;
 import org.apache.asterix.common.annotations.SkipSecondaryIndexSearchExpressionAnnotation;
 import org.apache.asterix.common.annotations.TypeDataGen;
 import org.apache.asterix.common.annotations.UndeclaredFieldsDataGen;
@@ -202,7 +204,6 @@
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.algebricks.core.algebra.expressions.BroadcastExpressionAnnotation;
 import org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionAnnotation;
-import org.apache.hyracks.algebricks.core.algebra.expressions.IndexedNLJoinExpressionAnnotation;
 import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
 import org.apache.hyracks.api.exceptions.SourceLocation;
 import org.apache.hyracks.api.exceptions.Warning;
@@ -370,6 +371,19 @@
         });
     }
 
+    private List<String> parseParenthesizedIdentifierList() throws CompilationException {
+        return parseImpl(new ParseFunction<List<String>>() {
+            @Override
+            public  List<String> parse() throws ParseException {
+                return SQLPPParser.this.ParenthesizedIdentifierList();
+            }
+        });
+    }
+
+    private static List<String> parseParenthesizedIdentifierList(String text) throws CompilationException {
+        return new SQLPPParser(text).parseParenthesizedIdentifierList();
+    }
+
     @Override
     public FunctionDecl parseFunctionBody(FunctionSignature signature, List<String> paramNames)
       throws CompilationException {
@@ -566,6 +580,46 @@
         }
     }
 
+    private IExpressionAnnotation parseExpressionAnnotation(Token hintToken) throws SqlppParseException {
+      try {
+        switch (hintToken.hint) {
+          case HASH_BROADCAST_JOIN_HINT:
+            return new BroadcastExpressionAnnotation(BroadcastExpressionAnnotation.BroadcastSide.RIGHT);
+          case INDEXED_NESTED_LOOP_JOIN_HINT:
+            if (hintToken.hintParams == null) {
+              return IndexedNLJoinExpressionAnnotation.INSTANCE_ANY_INDEX;
+            } else {
+              List<String> indexNames = parseParenthesizedIdentifierList(hintToken.hintParams);
+              return IndexedNLJoinExpressionAnnotation.newInstance(indexNames);
+            }
+          case RANGE_HINT:
+            Expression rangeExpr = parseExpression(hintToken.hintParams);
+            RangeMap rangeMap = RangeMapBuilder.parseHint(rangeExpr);
+            return new RangeAnnotation(rangeMap);
+          case SKIP_SECONDARY_INDEX_SEARCH_HINT:
+            if (hintToken.hintParams == null) {
+              return SkipSecondaryIndexSearchExpressionAnnotation.INSTANCE_ANY_INDEX;
+            } else {
+              List<String> indexNames = parseParenthesizedIdentifierList(hintToken.hintParams);
+              return SkipSecondaryIndexSearchExpressionAnnotation.newInstance(indexNames);
+            }
+          case USE_SECONDARY_INDEX_SEARCH_HINT:
+            if (hintToken.hintParams == null) {
+              throw new SqlppParseException(getSourceLocation(hintToken), "Expected index name");
+            } else {
+              List<String> indexNames = parseParenthesizedIdentifierList(hintToken.hintParams);
+              return SecondaryIndexSearchPreferenceAnnotation.newInstance(indexNames);
+            }
+          default:
+            throw new SqlppParseException(getSourceLocation(hintToken), "Unexpected hint " + hintToken.hint);
+        }
+      } catch (CompilationException e) {
+        SqlppParseException pe = new SqlppParseException(getSourceLocation(hintToken), e.getMessage());
+        pe.initCause(e);
+        throw pe;
+      }
+    }
+
     private void ensureNoTypeDeclsInFunction(String fnName, List<Pair<VarIdentifier, TypeExpression>> paramList,
       TypeExpression returnType, Token startToken) throws SqlppParseException {
       for (Pair<VarIdentifier, TypeExpression> p : paramList) {
@@ -2382,8 +2436,10 @@
 {
   // Note: there's a copy of this production in PrimaryExpr() (LOOKAHEAD for FunctionCallExpr())
   //       that copy must be kept in sync with this code
-  prefix = MultipartIdentifierWithHints(SqlppHint.INDEXED_NESTED_LOOP_JOIN_HINT,
-    SqlppHint.SKIP_SECONDARY_INDEX_SEARCH_HINT, SqlppHint.RANGE_HINT)
+  prefix = MultipartIdentifierWithHints(
+    SqlppHint.INDEXED_NESTED_LOOP_JOIN_HINT, SqlppHint.RANGE_HINT,
+    SqlppHint.SKIP_SECONDARY_INDEX_SEARCH_HINT, SqlppHint.USE_SECONDARY_INDEX_SEARCH_HINT
+  )
   (<SHARP> suffix = Identifier())?
   {
     FunctionName result = new FunctionName();
@@ -2445,6 +2501,21 @@
   )
 }
 
+List<String> ParenthesizedIdentifierList() throws ParseException:
+{
+  List<String> list = new ArrayList<String>();
+  String ident = null;
+}
+{
+  <LEFTPAREN>
+  ident = Identifier() { list.add(ident); }
+  ( <COMMA> ident = Identifier() { list.add(ident); } )*
+  <RIGHTPAREN>
+  {
+    return list;
+  }
+}
+
 Pair<Integer, Pair<List<String>, IndexedTypeExpression>> OpenField() throws ParseException:
 {
   IndexedTypeExpression fieldType = null;
@@ -2763,22 +2834,13 @@
     (
       LOOKAHEAD(2)( <LT> | <GT> | <LE> | <GE> | <EQ> | <NE> | <LG> |<SIMILAR> | (<NOT> { not = true; })? <IN>)
         {
-          Token hintToken = fetchHint(token, SqlppHint.INDEXED_NESTED_LOOP_JOIN_HINT,
-            SqlppHint.SKIP_SECONDARY_INDEX_SEARCH_HINT, SqlppHint.HASH_BROADCAST_JOIN_HINT);
+          Token hintToken = fetchHint(token,
+            SqlppHint.HASH_BROADCAST_JOIN_HINT, SqlppHint.INDEXED_NESTED_LOOP_JOIN_HINT,
+            SqlppHint.SKIP_SECONDARY_INDEX_SEARCH_HINT, SqlppHint.USE_SECONDARY_INDEX_SEARCH_HINT
+          );
           if (hintToken != null) {
-            switch (hintToken.hint) {
-              case INDEXED_NESTED_LOOP_JOIN_HINT:
-                annotation = IndexedNLJoinExpressionAnnotation.INSTANCE;
-                break;
-              case SKIP_SECONDARY_INDEX_SEARCH_HINT:
-                annotation = SkipSecondaryIndexSearchExpressionAnnotation.INSTANCE;
-                break;
-              case HASH_BROADCAST_JOIN_HINT:
-                annotation = new BroadcastExpressionAnnotation(BroadcastExpressionAnnotation.BroadcastSide.RIGHT);
-                break;
-            }
+            annotation = parseExpressionAnnotation(hintToken);
           }
-
           String operator = token.image.toLowerCase();
           if (operator.equals("<>")){
               operator = "!=";
@@ -2826,17 +2888,12 @@
       LOOKAHEAD(2)
       (<NOT> { not = true; })? <BETWEEN>
         {
-          Token hintToken = fetchHint(token, SqlppHint.INDEXED_NESTED_LOOP_JOIN_HINT,
-            SqlppHint.SKIP_SECONDARY_INDEX_SEARCH_HINT);
+          Token hintToken = fetchHint(token,
+            SqlppHint.INDEXED_NESTED_LOOP_JOIN_HINT, SqlppHint.SKIP_SECONDARY_INDEX_SEARCH_HINT,
+            SqlppHint.USE_SECONDARY_INDEX_SEARCH_HINT
+          );
           if (hintToken != null) {
-            switch (hintToken.hint) {
-              case INDEXED_NESTED_LOOP_JOIN_HINT:
-                annotation = IndexedNLJoinExpressionAnnotation.INSTANCE;
-                break;
-              case SKIP_SECONDARY_INDEX_SEARCH_HINT:
-                annotation = SkipSecondaryIndexSearchExpressionAnnotation.INSTANCE;
-                break;
-            }
+            annotation = parseExpressionAnnotation(hintToken);
           }
           String operator = token.image.toLowerCase();
           if(not){
@@ -3515,27 +3572,9 @@
     } else {
       CallExpr callExpr = new CallExpr(signature, argList, filterExpr);
       if (funcName.hintToken != null) {
-        switch (funcName.hintToken.hint) {
-          case INDEXED_NESTED_LOOP_JOIN_HINT:
-            callExpr.addHint(IndexedNLJoinExpressionAnnotation.INSTANCE);
-            break;
-          case SKIP_SECONDARY_INDEX_SEARCH_HINT:
-            callExpr.addHint(SkipSecondaryIndexSearchExpressionAnnotation.INSTANCE);
-            break;
-          case RANGE_HINT:
-            try {
-              Expression rangeExpr = parseExpression(funcName.hintToken.hintParams);
-              RangeMap rangeMap = RangeMapBuilder.parseHint(rangeExpr);
-              RangeAnnotation rangeAnn = new RangeAnnotation(rangeMap);
-              callExpr.addHint(rangeAnn);
-            } catch (CompilationException e) {
-              {
-                  SqlppParseException e2 = new SqlppParseException(getSourceLocation(funcName.hintToken), e.getMessage());
-                  e2.initCause(e);
-                  throw e2;
-              }
-            }
-            break;
+        IExpressionAnnotation annotation = parseExpressionAnnotation(funcName.hintToken);
+        if (annotation != null) {
+          callExpr.addHint(annotation);
         }
       }
       FunctionMapUtil.normalizedListInputFunctions(callExpr);