/**
   * Analye EQUALS (=) node.
   *
   * @param equalsNode - node to analyze
   * @param queryGraph - store relationships between stream properties
   */
  protected static void analyzeEqualsNode(
      ExprEqualsNode equalsNode, QueryGraph queryGraph, boolean isOuterJoin) {
    if ((equalsNode.getChildNodes()[0] instanceof ExprIdentNode)
        && (equalsNode.getChildNodes()[1] instanceof ExprIdentNode)) {
      ExprIdentNode identNodeLeft = (ExprIdentNode) equalsNode.getChildNodes()[0];
      ExprIdentNode identNodeRight = (ExprIdentNode) equalsNode.getChildNodes()[1];

      if (identNodeLeft.getStreamId() != identNodeRight.getStreamId()) {
        queryGraph.addStrictEquals(
            identNodeLeft.getStreamId(),
            identNodeLeft.getResolvedPropertyName(),
            identNodeLeft,
            identNodeRight.getStreamId(),
            identNodeRight.getResolvedPropertyName(),
            identNodeRight);
      }

      return;
    }
    if (isOuterJoin) { // outerjoins don't use constants or one-way expression-derived information
                       // to evaluate join
      return;
    }

    // handle constant-compare or transformation case
    int indexedStream = -1;
    String indexedProp = null;
    ExprNode exprNodeNoIdent = null;

    if (equalsNode.getChildNodes()[0] instanceof ExprIdentNode) {
      ExprIdentNode identNode = (ExprIdentNode) equalsNode.getChildNodes()[0];
      indexedStream = identNode.getStreamId();
      indexedProp = identNode.getResolvedPropertyName();
      exprNodeNoIdent = equalsNode.getChildNodes()[1];
    } else if (equalsNode.getChildNodes()[1] instanceof ExprIdentNode) {
      ExprIdentNode identNode = (ExprIdentNode) equalsNode.getChildNodes()[1];
      indexedStream = identNode.getStreamId();
      indexedProp = identNode.getResolvedPropertyName();
      exprNodeNoIdent = equalsNode.getChildNodes()[0];
    }
    if (indexedStream == -1) {
      return; // require property of right/left side of equals
    }

    EligibilityDesc eligibility = EligibilityUtil.verifyInputStream(exprNodeNoIdent, indexedStream);
    if (!eligibility.getEligibility().isEligible()) {
      return;
    }

    if (eligibility.getEligibility() == Eligibility.REQUIRE_NONE) {
      queryGraph.addUnkeyedExpression(indexedStream, indexedProp, exprNodeNoIdent);
    } else {
      queryGraph.addKeyedExpression(
          indexedStream, indexedProp, eligibility.getStreamNum(), exprNodeNoIdent);
    }
  }
  private static void analyzeRelationalOpNode(ExprRelationalOpNode relNode, QueryGraph queryGraph) {
    if (((relNode.getChildNodes()[0] instanceof ExprIdentNode))
        && ((relNode.getChildNodes()[1] instanceof ExprIdentNode))) {
      ExprIdentNode identNodeLeft = (ExprIdentNode) relNode.getChildNodes()[0];
      ExprIdentNode identNodeRight = (ExprIdentNode) relNode.getChildNodes()[1];

      if (identNodeLeft.getStreamId() != identNodeRight.getStreamId()) {
        queryGraph.addRelationalOpStrict(
            identNodeLeft.getStreamId(),
            identNodeLeft.getResolvedPropertyName(),
            identNodeLeft,
            identNodeRight.getStreamId(),
            identNodeRight.getResolvedPropertyName(),
            identNodeRight,
            relNode.getRelationalOpEnum());
      }
      return;
    }

    int indexedStream = -1;
    String indexedProp = null;
    ExprNode exprNodeNoIdent = null;
    RelationalOpEnum relop = relNode.getRelationalOpEnum();

    if (relNode.getChildNodes()[0] instanceof ExprIdentNode) {
      ExprIdentNode identNode = (ExprIdentNode) relNode.getChildNodes()[0];
      indexedStream = identNode.getStreamId();
      indexedProp = identNode.getResolvedPropertyName();
      exprNodeNoIdent = relNode.getChildNodes()[1];
    } else if (relNode.getChildNodes()[1] instanceof ExprIdentNode) {
      ExprIdentNode identNode = (ExprIdentNode) relNode.getChildNodes()[1];
      indexedStream = identNode.getStreamId();
      indexedProp = identNode.getResolvedPropertyName();
      exprNodeNoIdent = relNode.getChildNodes()[0];
      relop = relop.reversed();
    }
    if (indexedStream == -1) {
      return; // require property of right/left side of equals
    }

    EligibilityDesc eligibility = EligibilityUtil.verifyInputStream(exprNodeNoIdent, indexedStream);
    if (!eligibility.getEligibility().isEligible()) {
      return;
    }

    queryGraph.addRelationalOp(
        indexedStream, indexedProp, eligibility.getStreamNum(), exprNodeNoIdent, relop);
  }
  public void testAnalyze() throws Exception {
    List<OuterJoinDesc> descList = new LinkedList<OuterJoinDesc>();
    descList.add(
        SupportOuterJoinDescFactory.makeDesc(
            "intPrimitive", "s0", "intBoxed", "s1", OuterJoinType.LEFT));
    descList.add(
        SupportOuterJoinDescFactory.makeDesc(
            "simpleProperty", "s2", "theString", "s1", OuterJoinType.LEFT));
    // simpleProperty in s2

    QueryGraph graph = new QueryGraph(3);
    OuterJoinAnalyzer.analyze(descList, graph);
    assertEquals(3, graph.getNumStreams());

    assertTrue(graph.isNavigableAtAll(0, 1));
    assertEquals(1, QueryGraphTestUtil.getStrictKeyProperties(graph, 0, 1).length);
    assertEquals("intPrimitive", QueryGraphTestUtil.getStrictKeyProperties(graph, 0, 1)[0]);
    assertEquals(1, QueryGraphTestUtil.getStrictKeyProperties(graph, 1, 0).length);
    assertEquals("intBoxed", QueryGraphTestUtil.getStrictKeyProperties(graph, 1, 0)[0]);

    assertTrue(graph.isNavigableAtAll(1, 2));
    assertEquals("theString", QueryGraphTestUtil.getStrictKeyProperties(graph, 1, 2)[0]);
    assertEquals("simpleProperty", QueryGraphTestUtil.getStrictKeyProperties(graph, 2, 1)[0]);
  }
  public TestAlgoCollector assertDistance(
      AlgoHelperEntry algoEntry, List<QueryResult> queryList, OneRun oneRun) {
    List<Path> altPaths = new ArrayList<Path>();
    QueryGraph queryGraph = new QueryGraph(algoEntry.getQueryGraph());
    queryGraph.lookup(queryList);
    AlgorithmOptions opts = algoEntry.opts;
    FlagEncoder encoder = opts.getFlagEncoder();
    if (encoder.supports(TurnWeighting.class))
      algoEntry.setAlgorithmOptions(
          AlgorithmOptions.start(opts)
              .weighting(
                  new TurnWeighting(
                      opts.getWeighting(),
                      opts.getFlagEncoder(),
                      (TurnCostExtension) queryGraph.getExtension()))
              .build());

    for (int i = 0; i < queryList.size() - 1; i++) {
      RoutingAlgorithm algo = algoEntry.createAlgo(queryGraph);
      Path path =
          algo.calcPath(queryList.get(i).getClosestNode(), queryList.get(i + 1).getClosestNode());
      // System.out.println(path.calcInstructions().createGPX("temp", 0, "GMT"));
      altPaths.add(path);
    }

    PathMerger pathMerger =
        new PathMerger().setCalcPoints(true).setSimplifyResponse(false).setEnableInstructions(true);
    AltResponse rsp = new AltResponse();
    pathMerger.doWork(rsp, altPaths, trMap.getWithFallBack(Locale.US));

    if (rsp.hasErrors()) {
      errors.add(
          algoEntry
              + " response contains errors. Expected distance: "
              + rsp.getDistance()
              + ", expected points: "
              + oneRun
              + ". "
              + queryList
              + ", errors:"
              + rsp.getErrors());
      return this;
    }

    PointList pointList = rsp.getPoints();
    double tmpDist = pointList.calcDistance(distCalc);
    if (Math.abs(rsp.getDistance() - tmpDist) > 2) {
      errors.add(
          algoEntry
              + " path.getDistance was  "
              + rsp.getDistance()
              + "\t pointList.calcDistance was "
              + tmpDist
              + "\t (expected points "
              + oneRun.getLocs()
              + ", expected distance "
              + oneRun.getDistance()
              + ") "
              + queryList);
    }

    if (Math.abs(rsp.getDistance() - oneRun.getDistance()) > 2) {
      errors.add(
          algoEntry
              + " returns path not matching the expected distance of "
              + oneRun.getDistance()
              + "\t Returned was "
              + rsp.getDistance()
              + "\t (expected points "
              + oneRun.getLocs()
              + ", was "
              + pointList.getSize()
              + ") "
              + queryList);
    }

    // There are real world instances where A-B-C is identical to A-C (in meter precision).
    if (Math.abs(pointList.getSize() - oneRun.getLocs()) > 1) {
      errors.add(
          algoEntry
              + " returns path not matching the expected points of "
              + oneRun.getLocs()
              + "\t Returned was "
              + pointList.getSize()
              + "\t (expected distance "
              + oneRun.getDistance()
              + ", was "
              + rsp.getDistance()
              + ") "
              + queryList);
    }
    return this;
  }