content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF 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 org.apache.chemistry.opencmis.inmemory.query;
  
  import static org.junit.Assert.assertEquals;
  import static org.junit.Assert.assertFalse;
  import static org.junit.Assert.assertNotNull;
  import static org.junit.Assert.assertNull;
  import static org.junit.Assert.assertTrue;
  import static org.junit.Assert.fail;
  
  import java.util.List;
  import java.util.Map;
  
  import org.antlr.runtime.FailedPredicateException;
  import org.antlr.runtime.RecognitionException;
  import org.antlr.runtime.tree.CommonTree;
  import org.antlr.runtime.tree.Tree;
  import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
  import org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer;
  import org.apache.chemistry.opencmis.server.support.query.CmisQueryWalker;
  import org.apache.chemistry.opencmis.server.support.query.CmisSelector;
  import org.apache.chemistry.opencmis.server.support.query.ColumnReference;
  import org.apache.chemistry.opencmis.server.support.query.FunctionReference;
  import org.apache.chemistry.opencmis.server.support.query.QueryObject;
  import org.apache.chemistry.opencmis.server.support.query.QueryObject.SortSpec;
  import org.apache.chemistry.opencmis.server.support.query.QueryUtilStrict;
  import org.apache.chemistry.opencmis.server.support.query.TextSearchLexer;
  import org.junit.Before;
  import org.junit.Test;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  public class QueryParseTest extends AbstractQueryTest {
  
      private static final Logger LOG = LoggerFactory.getLogger(QueryParseTest.class);
  
      @Before
      public void setUp() {
          // initialize query object, we do not need a type manager for just
          // testing parsing
          super.setUp(null, null);
      }
  
      @Test
      public void simpleFailTest() {
          String statement = "SELECT * TO MyType ORDER BY abc.def ASC";
          try {
              traverseStatement(statement);
              fail("Errornous statement should throw exception.");
          } catch (Exception e) {
              LOG.debug("Exception in simpleFailTest: " + e);
          }
      }
  
      public void simpleSelectTest1() {
          String statement = "SELECT SCORE() FROM cmis:document";
  
          QueryUtilStrict queryUtil = traverseStatementAndCatchExc(statement);
          assertNotNull(queryUtil);
          assertNotNull(queryUtil.getWalker());
          assertNotNull(queryUtil.getQueryObject());
          QueryObject queryObj = queryUtil.getQueryObject();
  
          List<CmisSelector> selects = queryObj.getSelectReferences();
          assertTrue(1 == selects.size());
          assertTrue(selects.get(0) instanceof FunctionReference);
  
          FunctionReference funcRef = ((FunctionReference) selects.get(0));
          assertTrue(FunctionReference.CmisQlFunction.SCORE == funcRef.getFunction());
      }
  
      @Test
      public void simpleSelectTest2() {
          String statement = "SELECT abc FROM cmis:document";
          QueryUtilStrict queryUtil = traverseStatementAndCatchExc(statement);
          assertNotNull(queryUtil);
          assertNotNull(queryUtil.getWalker());
          assertNotNull(queryUtil.getQueryObject());
          QueryObject queryObj = queryUtil.getQueryObject();
  
          List<CmisSelector> selects = queryObj.getSelectReferences();
          assertTrue(1 == selects.size());
          // nothing should be in where references
          assertTrue(0 == queryObj.getWhereReferences().size());
  
          ColumnReference colRef = ((ColumnReference) selects.get(0));
          assertTrue(selects.get(0) instanceof ColumnReference);
          assertEquals("abc", colRef.getPropertyQueryName());
      }
  
      @Test
      public void simpleSelectTest3() {
          String statement = "SELECT t1.abc FROM cmis:document";
          QueryUtilStrict queryUtil = traverseStatementAndCatchExc(statement);
          assertNotNull(queryUtil);
          assertNotNull(queryUtil.getWalker());
          assertNotNull(queryUtil.getQueryObject());
          QueryObject queryObj = queryUtil.getQueryObject();
  
          List<CmisSelector> selects = queryObj.getSelectReferences();
          assertTrue(1 == selects.size());
          // nothing should be in where references
          assertTrue(0 == queryObj.getWhereReferences().size());
          assertTrue(selects.get(0) instanceof ColumnReference);
  
          ColumnReference colRef = ((ColumnReference) selects.get(0));
          assertEquals("t1", colRef.getQualifier());
          assertEquals("abc", colRef.getPropertyQueryName());
      }
  
      @Test
      public void simpleSelectTest4() {
          String statement = "SELECT * FROM cmis:document";
          QueryUtilStrict queryUtil = traverseStatementAndCatchExc(statement);
          assertNotNull(queryUtil);
          assertNotNull(queryUtil.getWalker());
          assertNotNull(queryUtil.getQueryObject());
          QueryObject queryObj = queryUtil.getQueryObject();
  
          List<CmisSelector> selects = queryObj.getSelectReferences();
          assertTrue(1 == selects.size());
          // nothing should be in where references
          assertTrue(0 == queryObj.getWhereReferences().size());
  
          ColumnReference colRef = ((ColumnReference) selects.get(0));
          assertTrue(selects.get(0) instanceof ColumnReference);
          assertEquals(null, colRef.getQualifier());
          assertEquals("*", colRef.getPropertyQueryName());
      }
  
      @Test
      public void simpleSelectTest5() {
          String statement = "SELECT t1.* FROM cmis:document";
          QueryUtilStrict queryUtil = traverseStatementAndCatchExc(statement);
          assertNotNull(queryUtil);
          assertNotNull(queryUtil.getWalker());
          assertNotNull(queryUtil.getQueryObject());
          QueryObject queryObj = queryUtil.getQueryObject();
  
          List<CmisSelector> selects = queryObj.getSelectReferences();
          assertTrue(1 == selects.size());
          // nothing should be in where references
          assertTrue(0 == queryObj.getWhereReferences().size());
          assertTrue(selects.get(0) instanceof ColumnReference);
  
          ColumnReference colRef = ((ColumnReference) selects.get(0));
          assertEquals("t1", colRef.getQualifier());
          assertEquals("*", colRef.getPropertyQueryName());
      }
  
      @Test
      public void simpleSelectTest6() {
          String statement = "SELECT t2.aaa myalias FROM cmis:document";
          QueryUtilStrict queryUtil = traverseStatementAndCatchExc(statement);
          assertNotNull(queryUtil);
          assertNotNull(queryUtil.getWalker());
          assertNotNull(queryUtil.getQueryObject());
          QueryObject queryObj = queryUtil.getQueryObject();
  
          List<CmisSelector> selects = queryObj.getSelectReferences();
          assertTrue(1 == selects.size());
          // nothing should be in where references
          assertTrue(0 == queryObj.getWhereReferences().size());
          assertTrue(selects.get(0) instanceof ColumnReference);
  
          ColumnReference colRef = ((ColumnReference) selects.get(0));
          assertEquals("t2", colRef.getQualifier());
          assertEquals("aaa", colRef.getPropertyQueryName());
      }
  
      @Test
      public void simpleSelectTest7() {
          // error processing
          String statement = "SELECTXXX t2.aaa myalias FROM cmis:document WHERE a < t1";
          try {
              traverseStatement(statement);
              fail("Walking of statement should with RecognitionException but succeeded");
          } catch (Exception e) {
              assertTrue(e instanceof CmisInvalidArgumentException);
          }
      }
  
      @Test
      public void simpleFromTest1() {
          String statement = "SELECT * FROM MyType MyAlias";
          QueryUtilStrict queryUtil = traverseStatementAndCatchExc(statement);
          assertNotNull(queryUtil);
          assertNotNull(queryUtil.getWalker());
          assertNotNull(queryUtil.getQueryObject());
          QueryObject queryObj = queryUtil.getQueryObject();
  
          Map<String, String> types = queryObj.getTypes();
          assertTrue(1 == types.size());
  
          String key = types.keySet().iterator().next();
          assertEquals("MyAlias", key);
          assertEquals("MyType", types.get(key));
      }
  
      @Test
      public void simpleFromTest2() {
          String statement = "SELECT * FROM MyType";
          QueryUtilStrict queryUtil = traverseStatementAndCatchExc(statement);
          assertNotNull(queryUtil);
          assertNotNull(queryUtil.getWalker());
          assertNotNull(queryUtil.getQueryObject());
          QueryObject queryObj = queryUtil.getQueryObject();
  
          Map<String, String> types = queryObj.getTypes();
          assertTrue(1 == types.size());
  
          String key = types.keySet().iterator().next();
          assertEquals("MyType", key);
          assertEquals("MyType", types.get(key));
      }
  
      @Test
      public void simpleFromTest3() {
          String statement = "SELECT t2.aaa FROM MyType abc123";
          QueryUtilStrict queryUtil = traverseStatementAndCatchExc(statement);
          assertNotNull(queryUtil);
          assertNotNull(queryUtil.getWalker());
          assertNotNull(queryUtil.getQueryObject());
          QueryObject queryObj = queryUtil.getQueryObject();
  
          Map<String, String> types = queryObj.getTypes();
          assertTrue(1 == types.size());
  
          String key = types.keySet().iterator().next();
          assertEquals("abc123", key);
          assertEquals("MyType", types.get(key));
      }
  
      @Test
      public void simpleFromTest4() {
          String statement = "SELECT X.aaa FROM MyType AS X WHERE 10 = ANY X.aaa ";
          QueryUtilStrict queryUtil = traverseStatementAndCatchExc(statement);
          assertNotNull(queryUtil);
          assertNotNull(queryUtil.getWalker());
          assertNotNull(queryUtil.getQueryObject());
          QueryObject queryObj = queryUtil.getQueryObject();
  
          Map<String, String> types = queryObj.getTypes();
          assertTrue(1 == types.size());
  
          String key = types.keySet().iterator().next();
          assertEquals("X", key);
          assertEquals("MyType", types.get(key));
      }
  
      @Test
      public void simpleWhereTest() {
          String statement = "SELECT * FROM MyType WHERE MyProp1=123";
  
          QueryUtilStrict queryUtil = traverseStatementAndCatchExc(statement);
          assertNotNull(queryUtil);
          assertNotNull(queryUtil.getWalker());
          assertNotNull(queryUtil.getQueryObject());
          QueryObject queryObj = queryUtil.getQueryObject();
          CmisQueryWalker walker = queryUtil.getWalker();
  
          List<CmisSelector> whereRefs = queryObj.getWhereReferences();
          Map<Integer, CmisSelector> colRefs = queryObj.getColumnReferences();
          assertTrue(1 == whereRefs.size());
  
          CmisSelector value = whereRefs.iterator().next();
          assertTrue(value instanceof ColumnReference);
          assertEquals("MyProp1", ((ColumnReference) value).getPropertyQueryName());
          // only "*" should be in select references
          assertTrue(1 == queryObj.getSelectReferences().size());
  
          CommonTree tree = (CommonTree) walker.getTreeNodeStream().getTreeSource();
  
          // assertTrue(traverseTreeAndFindNodeInColumnMap(tree, colRefs));
          traverseTreeAndFindNodeInColumnMap2(tree, colRefs);
      }
  
      // check if the map containing all column references in the where clause has
      // an existing node as key
      private boolean traverseTreeAndFindNodeInColumnMap(Tree node, Map<Object, CmisSelector> colRefs) {
          boolean found = false;
  
          if (null != colRefs.get(node)) {
              return true;
          }
  
          int count = node.getChildCount();
          for (int i = 0; i < count && !found; i++) {
              Tree child = node.getChild(i);
              found = traverseTreeAndFindNodeInColumnMap(child, colRefs);
          }
          return found;
      }
  
      private boolean traverseTreeAndFindNodeInColumnMap2(Tree node, Object colRef) {
          int count = node.getChildCount();
          LOG.debug("  checking with: " + node + " identity hash code: " + System.identityHashCode(node));
          if (node == colRef) {
              return true;
          }
          boolean found = false;
          for (int i = 0; i < count && !found; i++) {
              Tree child = node.getChild(i);
              found = traverseTreeAndFindNodeInColumnMap2(child, colRef);
          }
          return found;
      }
  
      @Test
      public void simpleSortTest1() {
          String statement = "SELECT * FROM MyType ORDER BY abc.def ASC";
  
          QueryUtilStrict queryUtil = traverseStatementAndCatchExc(statement);
          assertNotNull(queryUtil);
          assertNotNull(queryUtil.getWalker());
          assertNotNull(queryUtil.getQueryObject());
          QueryObject queryObj = queryUtil.getQueryObject();
  
          List<SortSpec> orderBys = queryObj.getOrderBys();
          assertTrue(1 == orderBys.size());
  
          SortSpec sp = orderBys.get(0);
          assertTrue(sp.isAscending());
  
          CmisSelector sortSpec = sp.getSelector();
          assert (sortSpec instanceof ColumnReference);
          assertEquals("abc", ((ColumnReference) sortSpec).getQualifier());
          assertEquals("def", ((ColumnReference) sortSpec).getPropertyQueryName());
      }
  
      @Test
      public void simpleSortTest2() {
          String statement = "SELECT * FROM MyType ORDER BY def DESC";
          QueryUtilStrict queryUtil = traverseStatementAndCatchExc(statement);
          assertNotNull(queryUtil);
          assertNotNull(queryUtil.getWalker());
          assertNotNull(queryUtil.getQueryObject());
          QueryObject queryObj = queryUtil.getQueryObject();
  
          List<SortSpec> orderBys = queryObj.getOrderBys();
          assertTrue(1 == orderBys.size());
  
          SortSpec sp = orderBys.get(0);
          assertFalse(sp.isAscending());
  
          CmisSelector sortSpec = sp.getSelector();
          assert (sortSpec instanceof ColumnReference);
          assertNull(((ColumnReference) sortSpec).getQualifier());
          assertEquals("def", ((ColumnReference) sortSpec).getPropertyQueryName());
      }
  
      @Test
      public void printTreeTest() {
          // System.out.println("printTreeTest():");
          String statement = "SELECT p1, p2, p3.t3 mycol FROM MyType AS MyAlias WHERE p1='abc' and p2=123 ORDER BY abc.def ASC";
          try {
  
              CmisQueryWalker walker = getWalker(statement);
              Tree parserTree = (Tree) walker.getTreeNodeStream().getTreeSource();
              printTree(parserTree, statement);
  
          } catch (Exception e) {
              fail("Cannot parse query: " + statement + " (" + e + ")");
          }
      }
  
      @Test
      public void extractWhereTreeTest() {
          // System.out.println("extractWhereTreeTest():");
          String statement = "SELECT p1, p2, p3.t3 mycol FROM MyType AS MyAlias WHERE p1='abc' and p2=123 ORDER BY abc.def ASC";
  
          try {
              QueryUtilStrict queryUtil = traverseStatementAndCatchExc(statement);
              CmisQueryWalker walker = queryUtil.getWalker();
              Tree whereTree = walker.getWherePredicateTree(); // getWhereTree(parserTree);
              printTree(whereTree);
              LOG.info("Evaluate WHERE subtree: ...");
              evalWhereTree(whereTree);
          } catch (Exception e) {
              fail("Cannot parse query: " + statement + " (" + e + ")");
          }
      }
  
      @Test
      public void whereTestIn() {
          // System.out.println("extractWhereTestIN():");
          String statement = "SELECT p1 FROM MyType WHERE p1 IN ('Red', 'Green', 'Blue', 'Black')";
          checkTreeWhere(statement);
      }
  
      @Test
      public void whereTestEq() {
          String statement = "SELECT p1 FROM MyType WHERE p1='abc'";
          checkTreeWhere(statement);
      }
  
      @Test
      public void whereTestNotEq() {
          String statement = "SELECT p1 FROM MyType WHERE p1 <> 123";
          checkTreeWhere(statement);
      }
  
      @Test
      public void whereTestLT() {
          String statement = "SELECT p1 FROM MyType WHERE p1 < 123";
          checkTreeWhere(statement);
      }
  
      @Test
      public void whereTestGT() {
          String statement = "SELECT p1 FROM MyType WHERE p1 > 123";
          checkTreeWhere(statement);
      }
  
      @Test
      public void whereTestLTEQ() {
          String statement = "SELECT p1 FROM MyType WHERE p1 <= 123";
          checkTreeWhere(statement);
      }
  
      @Test
      public void whereTestGTEQ() {
          String statement = "SELECT p1 FROM MyType WHERE p1 >= 123";
          checkTreeWhere(statement);
      }
  
      @Test
      public void whereTestAnd() {
          String statement = "SELECT p1 FROM MyType WHERE p1=1 AND p2=2";
          checkTreeWhere(statement);
      }
  
      @Test
      public void whereTestOr() {
          String statement = "SELECT p1 FROM MyType WHERE p1='abc' OR p2=123";
          checkTreeWhere(statement);
      }
  
      @Test
      public void whereTestNot() {
          String statement = "SELECT p1 FROM MyType WHERE NOT p1 = 123";
          checkTreeWhere(statement);
      }
  
      @Test
      public void whereTestInFolder() {
          String statement = "SELECT p1 FROM MyType WHERE IN_FOLDER('myfolder')";
          checkTreeWhere(statement);
      }
  
      @Test
      public void whereTestInTree() {
          String statement = "SELECT p1 FROM MyType WHERE IN_TREE('myfolder')";
          checkTreeWhere(statement);
      }
  
      @Test
      public void whereTestAny() {
          String statement = "SELECT p1 FROM MyType WHERE 'Smith' = ANY Authors ";
          checkTreeWhere(statement);
      }
  
      @Test
      public void whereTestAnyIn() {
          String statement = "SELECT p1 FROM MyType WHERE ANY Colors IN ('Red', 'Green', 'Blue')";
          checkTreeWhere(statement);
      }
  
      @Test
      public void whereTestLike() {
          String statement = "SELECT p1 FROM MyType WHERE p1 LIKE 'abc*' ";
          checkTreeWhere(statement);
      }
  
      @Test
      public void whereTestTokensAfterStatement() {
          String statement = "SELECT p1 FROM MyType WHERE p1 LIKE 'abc*' IN_FOLDER('def')";
          try {
              checkTreeWhere(statement);
              fail("Parsing query with tokens after end of statement should fail.");
          } catch (Exception e) {
              LOG.debug("Exception is: ", e);
              assertTrue(e instanceof CmisInvalidArgumentException);
              assertTrue(e.getMessage().contains("missing EOF at "));
          }
      }
  
      @Test
      public void whereTestNotLike() {
          String statement = "SELECT p1 FROM MyType WHERE p1 NOT LIKE 'abc*'";
          checkTreeWhere(statement);
      }
  
      @Test
      public void whereTestNull() {
          String statement = "SELECT p1 FROM MyType WHERE p1 IS NULL";
          checkTreeWhere(statement);
      }
  
      @Test
      public void whereTestNotNull() {
          String statement = "SELECT p1 FROM MyType WHERE p1 IS NOT NULL";
          checkTreeWhere(statement);
      }
  
      @Test
      public void whereTestContains() {
          String statement = "SELECT p1 FROM MyType WHERE CONTAINS('Beethoven')";
          checkTreeWhere(statement);
          Tree tree = findSearchExpression(statement);
          printSearchTree(tree, statement);
          assertEquals("Beethoven", tree.getChild(0).getText());
      }
  
      @Test
      public void whereTestContainsNoFulltextParse() throws Exception {
          String statement = "SELECT p1 FROM MyType WHERE CONTAINS('Beethoven')";
          QueryUtilStrict queryUtil = new QueryUtilStrict(statement, typeManager, predicateWalker, false);
          queryUtil.parseStatement();
          assertNotNull(queryUtil);
          assertNotNull(queryUtil.getQueryObject());
          queryUtil.walkStatement();
          CmisQueryWalker walker = queryUtil.getWalker();
          assertNotNull(queryUtil.getWalker());
          Tree whereTree = walker.getWherePredicateTree();
          Tree tree = findTextSearchNode(whereTree);
          printSearchTree(tree, statement);
          // unparsed, still with quotes
          assertEquals("'Beethoven'", tree.getChild(0).getText());
      }
  
      @Test
      public void whereTestScore() {
          String statement = "SELECT p1 FROM MyType WHERE SCORE() = 100";
          checkTreeWhere(statement);
      }
  
      @Test
      public void whereTestParentheses() {
          String statement = "SELECT p1 FROM MyType WHERE (p1 IS NULL OR SCORE()=100) AND (p2=123 OR p3=456)";
          checkTreeWhere(statement);
      }
  
      @Test
      public void doubleFromTest() {
          String statement = "SELECT * FROM MyType JOIN YourType WHERE a='1'";
  
          QueryUtilStrict queryUtil = traverseStatementAndCatchExc(statement);
          assertNotNull(queryUtil);
          assertNotNull(queryUtil.getWalker());
          assertNotNull(queryUtil.getQueryObject());
          CmisQueryWalker walker = queryUtil.getWalker();
          assertNotNull(walker);
  
          Map<String, String> types = queryUtil.getQueryObject().getTypes();
          assertTrue(2 == types.size());
      }
  
      @Test
      public void duplicatedAliasTestSelect() {
          String statement = "SELECT p1.T1 MyAlias, p2.T1 AS MyAlias FROM T1";
          try {
              traverseStatement(statement);
          } catch (Exception e) {
              assertTrue(e.getMessage().contains("more than once as alias in a select"));
          }
      }
  
      @Test
      public void duplicatedAliasTestFrom() {
          String statement = "SELECT * FROM T1 MyAlias JOIN T2 AS MyAlias";
          QueryUtilStrict queryUtil = null;
          try {
              queryUtil = new QueryUtilStrict(statement, typeManager, predicateWalker);
              queryUtil.processStatement();
              fail("Parsing statement " + statement + " should fail.");
          } catch (RecognitionException e) {
              assertTrue(e instanceof FailedPredicateException);
              LOG.debug("duplicatedAliasTestFrom(), exception: " + e);
              // walker.reportError(e);
              String errorMessage = queryUtil.getErrorMessage(e);
              LOG.debug("");
              LOG.debug("duplicatedAliasTestFrom(), error message: " + errorMessage);
              assertTrue(e.toString().contains("more than once as alias in a from"));
              assertTrue(errorMessage.contains("more than once as alias in a from"));
          } catch (Exception e) {
              fail("Parsing statement " + statement + " should fail with RecognitionException, but was: " + e.getClass());
          }
      }
  
      @Test
      public void whereTestContains2() {
          String statement = "SELECT p1 FROM MyType WHERE CONTAINS('Beethoven OR \"Johann Sebastian\" Mozart -Cage AND Orff')";
          checkTreeWhere(statement);
          Tree tree = findSearchExpression(statement);
          printSearchTree(tree, statement);
      }
  
      @Test
      public void whereTestMultipleContains2() {
          String statement = "SELECT p1 FROM MyType WHERE CONTAINS('Beethoven') AND CONTAINS('Bach')";
          checkTreeWhere(statement);
          int noContains = getNumberOfSearchExpression(statement);
          assertTrue(2 == noContains);
      }
  
      private void checkTreeWhere(String statement) {
          LOG.info("\ncheckTreeWhere: " + statement);
          QueryUtilStrict queryUtil = traverseStatementAndCatchExc(statement);
          Tree whereTree = queryUtil.getWalker().getWherePredicateTree();
          evalWhereTree(whereTree);
      }
  
      private Tree findSearchExpression(String statement) {
          QueryUtilStrict queryUtil = traverseStatementAndCatchExc(statement);
          Tree whereTree = queryUtil.getWalker().getWherePredicateTree();
          return findTextSearchNode(whereTree);
      }
  
      private int getNumberOfSearchExpression(String statement) {
          QueryUtilStrict queryUtil = traverseStatementAndCatchExc(statement);
          return queryUtil.getWalker().getNumberOfContainsClauses();
      }
  
      private Tree findTextSearchNode(Tree node) {
          int count = node.getChildCount();
          if (node.getType() == CmisQlStrictLexer.CONTAINS) {
              return node;
          } else {
              for (int i = 0; i < count; i++) {
                  Tree child = node.getChild(i);
                  node = findTextSearchNode(child); // recursive descent
                  if (null != node) {
                      return node;
                  }
              }
              return null;
          }
      }
  
      private void evalWhereTree(Tree root) {
          int count = root.getChildCount();
          if (root.getType() == CmisQlStrictLexer.CONTAINS) {
              evalSearchExprTree(root);
          } else {
              for (int i = 0; i < count; i++) {
                  Tree child = root.getChild(i);
                  evaluateWhereNode(child);
                  evalWhereTree(child); // recursive descent
              }
          }
      }
  
      private void evalSearchExprTree(Tree root) {
          int count = root.getChildCount();
          for (int i = 0; i < count; i++) {
              Tree child = root.getChild(i);
              evaluateSearchExprNode(child);
              evalSearchExprTree(child); // recursive descent
          }
      }
  
      private void printTree(Tree tree, String statement) {
          LOG.info("Printing the abstract syntax tree for statement:");
          LOG.info("  " + statement);
          printTree(tree);
      }
  
      private int indent = 1;
  
      private String indentString() {
          StringBuilder sb = new StringBuilder();
          for (int i = 0; i < indent; ++i) {
              sb.append("  ");
          }
          return sb.toString();
      }
  
      private void printTree(Tree node) {
          LOG.info(indentString() + printNode(node));
          ++indent;
          int count = node.getChildCount();
          for (int i = 0; i < count; i++) {
              Tree child = node.getChild(i);
              printTree(child);
          }
          --indent;
      }
  
      private static String printNode(Tree node) {
          switch (node.getType()) {
          case CmisQlStrictLexer.TABLE:
              return "#TABLE";
          case CmisQlStrictLexer.COL:
              return "#COL";
          case CmisQlStrictLexer.IN_LIST:
              return "#IN_LIST";
          case CmisQlStrictLexer.SEL_LIST:
              return "#SEL_LIST";
          case CmisQlStrictLexer.EQ_ANY:
              return "#EQ_ANY";
          case CmisQlStrictLexer.NOT_LIKE:
              return "#NOT_LIKE";
          case CmisQlStrictLexer.NOT_IN:
              return "#NOT_IN";
          case CmisQlStrictLexer.IN_ANY:
              return "#IN_ANY";
          case CmisQlStrictLexer.NOT_IN_ANY:
              return "#NOT_IN_ANY";
          case CmisQlStrictLexer.IS_NULL:
              return "#IS_NULL";
          case CmisQlStrictLexer.IS_NOT_NULL:
              return "#IS_NOT_NULL";
          case CmisQlStrictLexer.ORDER_BY:
              return "#ORDER_BY";
  
          case CmisQlStrictLexer.WHERE:
          case CmisQlStrictLexer.LT:
          case CmisQlStrictLexer.STAR:
          case CmisQlStrictLexer.BOOL_LIT:
          case CmisQlStrictLexer.INNER:
          case CmisQlStrictLexer.TIME_LIT:
          case CmisQlStrictLexer.ORDER:
          case CmisQlStrictLexer.STRING_LIT:
          case CmisQlStrictLexer.CONTAINS:
          case CmisQlStrictLexer.ExactNumLit:
          case CmisQlStrictLexer.LTEQ:
          case CmisQlStrictLexer.NOT:
          case CmisQlStrictLexer.ID:
          case CmisQlStrictLexer.AND:
          case CmisQlStrictLexer.EOF:
          case CmisQlStrictLexer.AS:
          case CmisQlStrictLexer.IN:
          case CmisQlStrictLexer.LPAR:
          case CmisQlStrictLexer.Digits:
          case CmisQlStrictLexer.COMMA:
          case CmisQlStrictLexer.IS:
          case CmisQlStrictLexer.LEFT:
          case CmisQlStrictLexer.Sign:
          case CmisQlStrictLexer.EQ:
          case CmisQlStrictLexer.DOT:
          case CmisQlStrictLexer.NUM_LIT:
          case CmisQlStrictLexer.SELECT:
          case CmisQlStrictLexer.LIKE:
          case CmisQlStrictLexer.OUTER:
          case CmisQlStrictLexer.BY:
          case CmisQlStrictLexer.ASC:
          case CmisQlStrictLexer.NULL:
          case CmisQlStrictLexer.ON:
          case CmisQlStrictLexer.RIGHT:
          case CmisQlStrictLexer.GTEQ:
          case CmisQlStrictLexer.ApproxNumLit:
          case CmisQlStrictLexer.JOIN:
          case CmisQlStrictLexer.IN_FOLDER:
          case CmisQlStrictLexer.WS:
          case CmisQlStrictLexer.NEQ:
          case CmisQlStrictLexer.ANY:
          case CmisQlStrictLexer.SCORE:
          case CmisQlStrictLexer.IN_TREE:
          case CmisQlStrictLexer.OR:
          case CmisQlStrictLexer.GT:
          case CmisQlStrictLexer.RPAR:
          case CmisQlStrictLexer.DESC:
          case CmisQlStrictLexer.FROM:
          case CmisQlStrictLexer.TIMESTAMP:
              return node.toString();
          default:
              return "[Unknown token: " + node.toString() + "]";
          }
      }
  
      private void printSearchTree(Tree tree, String searchExpr) {
          LOG.info("Printhing the abstract syntax tree for the search expression in CONTAINS :");
          LOG.info(searchExpr);
          printSearchTree(tree);
      }
  
      private void printSearchTree(Tree node) {
          LOG.info(indentString() + printSearchNode(node));
          ++indent;
          int count = node.getChildCount();
          for (int i = 0; i < count; i++) {
              Tree child = node.getChild(i);
              printSearchTree(child);
          }
          --indent;
      }
  
      private static String printSearchNode(Tree node) {
          switch (node.getType()) {
          case TextSearchLexer.TEXT_AND:
          case TextSearchLexer.TEXT_OR:
          case TextSearchLexer.TEXT_SEARCH_PHRASE_STRING_LIT:
          case TextSearchLexer.TEXT_SEARCH_WORD_LIT:
              return node.toString();
          case TextSearchLexer.TEXT_MINUS:
              return "MINUS";
          default:
              return "Unknown token: " + node.toString();
          }
      }
  
      // Ensure that we receive only valid tokens and nodes in the where clause:
      private void evaluateWhereNode(Tree node) {
          LOG.info("evaluating node: " + node.toString());
          switch (node.getType()) {
          case CmisQlStrictLexer.WHERE:
              break; // ignore
          case CmisQlStrictLexer.COL:
              evalColumn(node);
              break;
          case CmisQlStrictLexer.IN_LIST:
              evalInList(node);
              break;
          case CmisQlStrictLexer.IN_ANY:
              evalInAny(node);
              break;
          case CmisQlStrictLexer.EQ_ANY:
              evalEqAny(node);
              break;
          case CmisQlStrictLexer.NOT_LIKE:
              evalNotLike(node);
              break;
          case CmisQlStrictLexer.NOT_IN:
              evalNotIn(node);
              break;
          case CmisQlStrictLexer.IS_NULL:
              evalIsNull(node);
              break;
          case CmisQlStrictLexer.IS_NOT_NULL:
              evalIsNotNull(node);
              break;
          case CmisQlStrictLexer.LT:
              evalLessThan(node);
              break;
          case CmisQlStrictLexer.BOOL_LIT:
              evalBooleanLiteral(node);
              break;
          case CmisQlStrictLexer.TIME_LIT:
              evalTimeLiteral(node);
              break;
          case CmisQlStrictLexer.STRING_LIT:
              evalStringLiteral(node);
              break;
          case CmisQlStrictLexer.CONTAINS:
              evalContains(node);
              break;
          case CmisQlStrictLexer.ExactNumLit:
              evalExactNumLiteral(node);
              break;
          case CmisQlStrictLexer.LTEQ:
              evalLessOrEqual(node);
              break;
          case CmisQlStrictLexer.NOT:
              evalNot(node);
              break;
          case CmisQlStrictLexer.ID:
              evalId(node);
              break;
          case CmisQlStrictLexer.AND:
              evalAnd(node);
              break;
          case CmisQlStrictLexer.IN:
              evalIn(node);
              break;
          case CmisQlStrictLexer.EQ:
              evalEquals(node);
              break;
          case CmisQlStrictLexer.NUM_LIT:
              evalNumLiteral(node);
              break;
          case CmisQlStrictLexer.LIKE:
              evalLike(node);
              break;
          case CmisQlStrictLexer.NULL:
              evalNull(node);
              break;
          case CmisQlStrictLexer.GTEQ:
              evalGreaterThan(node);
              break;
          case CmisQlStrictLexer.ApproxNumLit:
              evalApproxNumLiteral(node);
              break;
          case CmisQlStrictLexer.IN_FOLDER:
              evalInFolder(node);
              break;
          case CmisQlStrictLexer.NEQ:
              evalNotEquals(node);
              break;
          case CmisQlStrictLexer.SCORE:
              evalScore(node);
              break;
          case CmisQlStrictLexer.IN_TREE:
              evalInTree(node);
              break;
          case CmisQlStrictLexer.OR:
              evalOr(node);
              break;
          case CmisQlStrictLexer.GT:
              evalGreaterThan(node);
              break;
          case CmisQlStrictLexer.TIMESTAMP:
              evalTimeLiteral(node);
              break;
          default:
              fail("[Unexpected node in WHERE clause: " + node.toString() + "]");
          }
      }
  
      // Ensure that we receive only valid tokens and nodes in the where clause:
      private void evaluateSearchExprNode(Tree node) {
          LOG.info("evaluating text search expression node: " + node.toString());
          switch (node.getType()) {
          case TextSearchLexer.TEXT_AND:
          case TextSearchLexer.TEXT_OR:
              assertTrue(node.getChildCount() >= 2);
              break;
          case TextSearchLexer.TEXT_MINUS:
              assertEquals(1, node.getChildCount());
              break;
          case TextSearchLexer.TEXT_SEARCH_PHRASE_STRING_LIT:
          case TextSearchLexer.TEXT_SEARCH_WORD_LIT:
              evalStringLiteral(node);
              break;
          default:
              fail("[Unexpected node in text search expression: " + node.toString() + "]");
          }
      }
  
      private void evalInAny(Tree node) {
      }
  
      private static void evalColumn(Tree node) {
          assertEquals(1, node.getChildCount());
          assertEquals(CmisQlStrictLexer.ID, node.getChild(0).getType());
      }
  
      private static void evalEquals(Tree node) {
          assertEquals(2, node.getChildCount());
      }
  
      private static void evalInFolder(Tree node) {
          assertEquals(1, node.getChildCount());
      }
  
      private static void evalApproxNumLiteral(Tree node) {
      }
  
      private static void evalNull(Tree node) {
          assertEquals(1, node.getChildCount());
      }
  
      private static void evalLike(Tree node) {
          assertEquals(2, node.getChildCount());
      }
  
      private static void evalNumLiteral(Tree node) {
          assertEquals(0, node.getChildCount());
      }
  
      private static void evalInList(Tree node) {
      }
  
      private static void evalEqAny(Tree node) {
      }
  
      private static void evalNotLike(Tree node) {
          assertEquals(2, node.getChildCount());
      }
  
      private static void evalNotIn(Tree node) {
      }
  
      private static void evalIsNull(Tree node) {
          assertEquals(1, node.getChildCount());
      }
  
      private static void evalIsNotNull(Tree node) {
          assertEquals(1, node.getChildCount());
      }
  
      private static void evalLessThan(Tree node) {
          assertEquals(2, node.getChildCount());
      }
  
      private static void evalBooleanLiteral(Tree node) {
          assertEquals(0, node.getChildCount());
      }
  
      private static void evalStringLiteral(Tree node) {
          assertEquals(0, node.getChildCount());
      }
  
      private static void evalContains(Tree node) {
          assertEquals(1, node.getChildCount());
      }
  
      private static void evalExactNumLiteral(Tree node) {
          assertEquals(0, node.getChildCount());
      }
  
      private static void evalLessOrEqual(Tree node) {
          assertEquals(2, node.getChildCount());
      }
  
      private static void evalNot(Tree node) {
          assertEquals(1, node.getChildCount());
      }
  
      private static void evalId(Tree node) {
          assertEquals(0, node.getChildCount());
      }
  
      private static void evalAnd(Tree node) {
          assertEquals(2, node.getChildCount());
      }
  
      private void evalIn(Tree node) {
      }
  
      private static void evalNotEquals(Tree node) {
          assertEquals(2, node.getChildCount());
      }
  
      private static void evalScore(Tree node) {
          assertEquals(0, node.getChildCount());
      }
  
      private static void evalInTree(Tree node) {
          assertEquals(1, node.getChildCount());
      }
  
      private static void evalOr(Tree node) {
          assertEquals(2, node.getChildCount());
      }
  
      private static void evalGreaterThan(Tree node) {
          assertEquals(2, node.getChildCount());
      }
  
      private static void evalTimeLiteral(Tree node) {
          assertEquals(0, node.getChildCount());
      }
  
  }
  