@Test
 public void testExplainAsXml() {
   String sql = "select 1 + 2, 3 from (values (true))";
   final RelNode rel = tester.convertSqlToRel(sql).rel;
   StringWriter sw = new StringWriter();
   PrintWriter pw = new PrintWriter(sw);
   RelXmlWriter planWriter = new RelXmlWriter(pw, SqlExplainLevel.EXPPLAN_ATTRIBUTES);
   rel.explain(planWriter);
   pw.flush();
   TestUtil.assertEqualsVerbose(
       "<RelNode type=\"LogicalProject\">\n"
           + "\t<Property name=\"EXPR$0\">\n"
           + "\t\t+(1, 2)\t</Property>\n"
           + "\t<Property name=\"EXPR$1\">\n"
           + "\t\t3\t</Property>\n"
           + "\t<Inputs>\n"
           + "\t\t<RelNode type=\"LogicalValues\">\n"
           + "\t\t\t<Property name=\"tuples\">\n"
           + "\t\t\t\t[{ true }]\t\t\t</Property>\n"
           + "\t\t\t<Inputs/>\n"
           + "\t\t</RelNode>\n"
           + "\t</Inputs>\n"
           + "</RelNode>\n",
       Util.toLinux(sw.toString()));
 }
  /**
   * Checks whether an exception matches the expected pattern. If <code>
   * sap</code> contains an error location, checks this too.
   *
   * @param ex Exception thrown
   * @param expectedMsgPattern Expected pattern
   * @param sap Query and (optional) position in query
   */
  public static void checkEx(
      Throwable ex, String expectedMsgPattern, SqlParserUtil.StringAndPos sap) {
    if (null == ex) {
      if (expectedMsgPattern == null) {
        // No error expected, and no error happened.
        return;
      } else {
        throw new AssertionError(
            "Expected query to throw exception, "
                + "but it did not; query ["
                + sap.sql
                + "]; expected ["
                + expectedMsgPattern
                + "]");
      }
    }
    Throwable actualException = ex;
    String actualMessage = actualException.getMessage();
    int actualLine = -1;
    int actualColumn = -1;
    int actualEndLine = 100;
    int actualEndColumn = 99;

    // Search for an CalciteContextException somewhere in the stack.
    CalciteContextException ece = null;
    for (Throwable x = ex; x != null; x = x.getCause()) {
      if (x instanceof CalciteContextException) {
        ece = (CalciteContextException) x;
        break;
      }
      if (x.getCause() == x) {
        break;
      }
    }

    // Search for a SqlParseException -- with its position set -- somewhere
    // in the stack.
    SqlParseException spe = null;
    for (Throwable x = ex; x != null; x = x.getCause()) {
      if ((x instanceof SqlParseException) && (((SqlParseException) x).getPos() != null)) {
        spe = (SqlParseException) x;
        break;
      }
      if (x.getCause() == x) {
        break;
      }
    }

    if (ece != null) {
      actualLine = ece.getPosLine();
      actualColumn = ece.getPosColumn();
      actualEndLine = ece.getEndPosLine();
      actualEndColumn = ece.getEndPosColumn();
      if (ece.getCause() != null) {
        actualException = ece.getCause();
        actualMessage = actualException.getMessage();
      }
    } else if (spe != null) {
      actualLine = spe.getPos().getLineNum();
      actualColumn = spe.getPos().getColumnNum();
      actualEndLine = spe.getPos().getEndLineNum();
      actualEndColumn = spe.getPos().getEndColumnNum();
      if (spe.getCause() != null) {
        actualException = spe.getCause();
        actualMessage = actualException.getMessage();
      }
    } else {
      final String message = ex.getMessage();
      if (message != null) {
        Matcher matcher = LINE_COL_TWICE_PATTERN.matcher(message);
        if (matcher.matches()) {
          actualLine = Integer.parseInt(matcher.group(1));
          actualColumn = Integer.parseInt(matcher.group(2));
          actualEndLine = Integer.parseInt(matcher.group(3));
          actualEndColumn = Integer.parseInt(matcher.group(4));
          actualMessage = matcher.group(5);
        } else {
          matcher = LINE_COL_PATTERN.matcher(message);
          if (matcher.matches()) {
            actualLine = Integer.parseInt(matcher.group(1));
            actualColumn = Integer.parseInt(matcher.group(2));
          }
        }
      }
    }

    if (null == expectedMsgPattern) {
      if (null != actualException) {
        actualException.printStackTrace();
        fail(
            "Validator threw unexpected exception"
                + "; query ["
                + sap.sql
                + "]; exception ["
                + actualMessage
                + "]; class ["
                + actualException.getClass()
                + "]; pos [line "
                + actualLine
                + " col "
                + actualColumn
                + " thru line "
                + actualLine
                + " col "
                + actualColumn
                + "]");
      }
    } else {
      if (null == actualException) {
        fail(
            "Expected validator to throw "
                + "exception, but it did not; query ["
                + sap.sql
                + "]; expected ["
                + expectedMsgPattern
                + "]");
      } else {
        String sqlWithCarets;
        if ((actualColumn <= 0)
            || (actualLine <= 0)
            || (actualEndColumn <= 0)
            || (actualEndLine <= 0)) {
          if (sap.pos != null) {
            AssertionError e =
                new AssertionError(
                    "Expected error to have position,"
                        + " but actual error did not: "
                        + " actual pos [line "
                        + actualLine
                        + " col "
                        + actualColumn
                        + " thru line "
                        + actualEndLine
                        + " col "
                        + actualEndColumn
                        + "]");
            e.initCause(actualException);
            throw e;
          }
          sqlWithCarets = sap.sql;
        } else {
          sqlWithCarets =
              SqlParserUtil.addCarets(
                  sap.sql, actualLine, actualColumn, actualEndLine, actualEndColumn + 1);
          if (sap.pos == null) {
            throw new AssertionError(
                "Actual error had a position, but expected error"
                    + " did not. Add error position carets to sql:\n"
                    + sqlWithCarets);
          }
        }
        if (actualMessage != null) {
          actualMessage = Util.toLinux(actualMessage);
        }
        if ((actualMessage == null) || !actualMessage.matches(expectedMsgPattern)) {
          actualException.printStackTrace();
          final String actualJavaRegexp =
              (actualMessage == null)
                  ? "null"
                  : TestUtil.quoteForJava(TestUtil.quotePattern(actualMessage));
          fail(
              "Validator threw different "
                  + "exception than expected; query ["
                  + sap.sql
                  + "];\n"
                  + " expected pattern ["
                  + expectedMsgPattern
                  + "];\n"
                  + " actual ["
                  + actualMessage
                  + "];\n"
                  + " actual as java regexp ["
                  + actualJavaRegexp
                  + "]; pos ["
                  + actualLine
                  + " col "
                  + actualColumn
                  + " thru line "
                  + actualEndLine
                  + " col "
                  + actualEndColumn
                  + "]; sql ["
                  + sqlWithCarets
                  + "]");
        } else if ((sap.pos != null)
            && ((actualLine != sap.pos.getLineNum())
                || (actualColumn != sap.pos.getColumnNum())
                || (actualEndLine != sap.pos.getEndLineNum())
                || (actualEndColumn != sap.pos.getEndColumnNum()))) {
          fail(
              "Validator threw expected "
                  + "exception ["
                  + actualMessage
                  + "];\nbut at pos [line "
                  + actualLine
                  + " col "
                  + actualColumn
                  + " thru line "
                  + actualEndLine
                  + " col "
                  + actualEndColumn
                  + "];\nsql ["
                  + sqlWithCarets
                  + "]");
        }
      }
    }
  }