gt:         ResultSetMetaData meta = rs.getMetaData();
context: /*
   * Copyright 2004-2013 H2 Group. Multiple-Licensed under the H2 License,
   * Version 1.0, and under the Eclipse Public License, Version 1.0
   * (http://h2database.com/html/license.html).
   * Initial Developer: H2 Group
   */
  package org.h2.test.synth;
  
  import java.sql.Connection;
  import java.sql.DriverManager;
  import java.sql.PreparedStatement;
  import java.sql.ResultSet;
  import java.sql.ResultSetMetaData;
  import java.sql.SQLException;
  import java.sql.Statement;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Random;
  
  import org.h2.test.TestBase;
  import org.h2.util.New;
  import org.h2.util.StringUtils;
  
  /**
   * A test that runs random join statements against two databases and compares
   * the results.
   */
  public class TestJoin extends TestBase {
  
      private final ArrayList<Connection> connections = New.arrayList();
      private Random random;
      private int paramCount;
      private StringBuilder buff;
  
      /**
       * Run just this test.
       *
       * @param a ignored
       */
      public static void main(String... a) throws Exception {
          TestBase.createCaller().init().test();
      }
  
      @Override
      public void test() throws Exception {
          testJoin();
      }
  
      private void testJoin() throws Exception {
          deleteDb("join");
          String shortestFailed = null;
  
          Connection c1 = getConnection("join");
          connections.add(c1);
  
          Class.forName("org.postgresql.Driver");
          Connection c2 = DriverManager.getConnection("jdbc:postgresql:test", "sa", "sa");
          connections.add(c2);
  
          // Class.forName("com.mysql.jdbc.Driver");
          // Connection c2 =
          // DriverManager.getConnection("jdbc:mysql://localhost/test", "sa",
          // "sa");
          // connections.add(c2);
  
          // Class.forName("org.hsqldb.jdbcDriver");
          // Connection c2 = DriverManager.getConnection("jdbc:hsqldb:join", "sa",
          // "");
          // connections.add(c2);
  
          /*
          DROP TABLE ONE;
          DROP TABLE TWO;
          CREATE TABLE ONE(A INT PRIMARY KEY, B INT);
          INSERT INTO ONE VALUES(0, NULL);
          INSERT INTO ONE VALUES(1, 0);
          INSERT INTO ONE VALUES(2, 1);
          INSERT INTO ONE VALUES(3, 4);
          CREATE TABLE TWO(A INT PRIMARY KEY, B INT);
          INSERT INTO TWO VALUES(0, NULL);
          INSERT INTO TWO VALUES(1, 0);
          INSERT INTO TWO VALUES(2, 2);
          INSERT INTO TWO VALUES(3, 3);
          INSERT INTO TWO VALUES(4, NULL);
          */
  
          execute("DROP TABLE ONE", null, true);
          execute("DROP TABLE TWO", null, true);
          execute("CREATE TABLE ONE(A INT PRIMARY KEY, B INT)", null);
          execute("INSERT INTO ONE VALUES(0, NULL)", null);
          execute("INSERT INTO ONE VALUES(1, 0)", null);
          execute("INSERT INTO ONE VALUES(2, 1)", null);
          execute("INSERT INTO ONE VALUES(3, 4)", null);
          execute("CREATE TABLE TWO(A INT PRIMARY KEY, B INT)", null);
          execute("INSERT INTO TWO VALUES(0, NULL)", null);
          execute("INSERT INTO TWO VALUES(1, 0)", null);
          execute("INSERT INTO TWO VALUES(2, 2)", null);
          execute("INSERT INTO TWO VALUES(3, 3)", null);
          execute("INSERT INTO TWO VALUES(4, NULL)", null);
          random = new Random();
          long startTime = System.currentTimeMillis();
          for (int i = 0;; i++) {
              paramCount = 0;
              buff = new StringBuilder();
              long time = System.currentTimeMillis();
              if (time - startTime > 5000) {
                  printTime("i:" + i);
                  startTime = time;
              }
              buff.append("SELECT ");
              int tables = 1 + random.nextInt(5);
              for (int j = 0; j < tables; j++) {
                  if (j > 0) {
                      buff.append(", ");
                  }
                  buff.append("T" + (char) ('0' + j) + ".A");
              }
              buff.append(" FROM ");
              appendRandomTable();
              buff.append(" T0 ");
              for (int j = 1; j < tables; j++) {
                  if (random.nextBoolean()) {
                      buff.append("INNER");
                  } else {
                      // if(random.nextInt(4)==1) {
                      // buff.append("RIGHT");
                      // } else {
                      buff.append("LEFT");
                      // }
                  }
                  buff.append(" JOIN ");
                  appendRandomTable();
                  buff.append(" T");
                  buff.append((char) ('0' + j));
                  buff.append(" ON ");
                  appendRandomCondition(j);
              }
              if (random.nextBoolean()) {
                  buff.append("WHERE ");
                  appendRandomCondition(tables - 1);
              }
              String sql = buff.toString();
              Object[] params = new Object[paramCount];
              for (int j = 0; j < paramCount; j++) {
                  params[j] = random.nextInt(4) == 1 ? null : random.nextInt(10) - 3;
              }
              try {
                  execute(sql, params);
              } catch (Exception e) {
                  if (shortestFailed == null || shortestFailed.length() > sql.length()) {
                      TestBase.logError("/*SHORT*/ " + sql, null);
                      shortestFailed = sql;
                  }
              }
          }
          // c1.close();
          // c2.close();
      }
  
      private void appendRandomTable() {
          if (random.nextBoolean()) {
              buff.append("ONE");
          } else {
              buff.append("TWO");
          }
      }
  
      private void appendRandomCondition(int j) {
          if (random.nextInt(10) == 1) {
              buff.append("NOT ");
              appendRandomCondition(j);
          } else if (random.nextInt(5) == 1) {
              buff.append("(");
              appendRandomCondition(j);
              if (random.nextBoolean()) {
                  buff.append(") OR (");
              } else {
                  buff.append(") AND (");
              }
              appendRandomCondition(j);
              buff.append(")");
          } else {
              if (j > 0 && random.nextBoolean()) {
                  buff.append("T" + (char) ('0' + j - 1) + ".A=T" + (char) ('0' + j) + ".A ");
              } else {
                  appendRandomConditionPart(j);
              }
          }
      }
  
      private void appendRandomConditionPart(int j) {
          int t1 = j <= 1 ? 0 : random.nextInt(j + 1);
          int t2 = j <= 1 ? 0 : random.nextInt(j + 1);
          String c1 = random.nextBoolean() ? "A" : "B";
          String c2 = random.nextBoolean() ? "A" : "B";
          buff.append("T" + (char) ('0' + t1));
          buff.append("." + c1);
          if (random.nextInt(4) == 1) {
              if (random.nextInt(5) == 1) {
                  buff.append(" IS NOT NULL");
              } else {
                  buff.append(" IS NULL");
              }
          } else {
              if (random.nextInt(5) == 1) {
                  switch (random.nextInt(5)) {
                  case 0:
                      buff.append(">");
                      break;
                  case 1:
                      buff.append("<");
                      break;
                  case 2:
                      buff.append("<=");
                      break;
                  case 3:
                      buff.append(">=");
                      break;
                  case 4:
                      buff.append("<>");
                      break;
                  default:
                  }
              } else {
                  buff.append("=");
              }
              if (random.nextBoolean()) {
                  buff.append("T" + (char) ('0' + t2));
                  buff.append("." + c2);
              } else {
                  buff.append(random.nextInt(5) - 1);
              }
          }
          buff.append(" ");
      }
  
      private void execute(String sql, Object[] params) {
          execute(sql, params, false);
      }
  
      private void execute(String sql, Object[] params, boolean ignoreDifference) {
          String first = null;
          for (int i = 0; i < connections.size(); i++) {
              Connection conn = connections.get(i);
              String s;
              try {
                  Statement stat;
                  boolean result;
                  if (params == null || params.length == 0) {
                      stat = conn.createStatement();
                      result = stat.execute(sql);
                  } else {
                      PreparedStatement prep = conn.prepareStatement(sql);
                      stat = prep;
                      for (int j = 0; j < params.length; j++) {
                          prep.setObject(j + 1, params[j]);
                      }
                      result = prep.execute();
                  }
                  if (result) {
                      ResultSet rs = stat.getResultSet();
                      s = "rs: " + readResult(rs);
                  } else {
                      s = "updateCount: " + stat.getUpdateCount();
                  }
              } catch (SQLException e) {
                  s = "exception";
              }
              if (i == 0) {
                  first = s;
              } else {
                  if (!ignoreDifference && !s.equals(first)) {
                      fail("FAIL s:" + s + " first:" + first + " sql:" + sql);
                  }
              }
          }
      }
  
      private static String readResult(ResultSet rs) throws SQLException {
          StringBuilder b = new StringBuilder();
  