Package net.hydromatic.optiq.test

Source Code of net.hydromatic.optiq.test.OptiqAssert$AssertQuery

/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde 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 net.hydromatic.optiq.test;

import net.hydromatic.linq4j.function.Function1;

import net.hydromatic.optiq.*;
import net.hydromatic.optiq.impl.AbstractSchema;
import net.hydromatic.optiq.impl.ViewTable;
import net.hydromatic.optiq.impl.clone.CloneSchema;
import net.hydromatic.optiq.impl.java.ReflectiveSchema;
import net.hydromatic.optiq.impl.jdbc.JdbcSchema;
import net.hydromatic.optiq.jdbc.MetaImpl;
import net.hydromatic.optiq.jdbc.OptiqConnection;
import net.hydromatic.optiq.runtime.Hook;

import org.eigenbase.rel.RelNode;
import org.eigenbase.relopt.RelOptUtil;
import org.eigenbase.util.*;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultiset;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.*;
import java.sql.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import javax.sql.DataSource;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

/**
* Fluid DSL for testing Optiq connections and queries.
*/
public class OptiqAssert {
  private OptiqAssert() {}

  /** Which database to use for tests that require a JDBC data source. By
   * default the test suite runs against the embedded hsqldb database.
   *
   * <p>We recommend that casual users use hsqldb, and frequent Optiq developers
   * use MySQL. The test suite runs faster against the MySQL database (mainly
   * because of the 0.1s versus 6s startup time). You have to populate MySQL
   * manually with the foodmart data set, otherwise there will be test failures.
   * To run against MySQL, specify '-Doptiq.test.db=mysql' on the java command
   * line.</p> */
  public static final ConnectionSpec CONNECTION_SPEC =
      Util.first(System.getProperty("optiq.test.db"), "hsqldb").equals("mysql")
          ? ConnectionSpec.MYSQL
          : ConnectionSpec.HSQLDB;

  /** Whether to enable slow tests. Default is false. */
  public static final boolean ENABLE_SLOW =
      Util.first(Boolean.getBoolean("optiq.test.slow"), false);

  private static final DateFormat UTC_DATE_FORMAT;
  private static final DateFormat UTC_TIME_FORMAT;
  private static final DateFormat UTC_TIMESTAMP_FORMAT;
  static {
    final TimeZone utc = TimeZone.getTimeZone("UTC");
    UTC_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
    UTC_DATE_FORMAT.setTimeZone(utc);
    UTC_TIME_FORMAT = new SimpleDateFormat("HH:mm:ss");
    UTC_TIME_FORMAT.setTimeZone(utc);
    UTC_TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
    UTC_TIMESTAMP_FORMAT.setTimeZone(utc);
  }

  /** Implementation of {@link AssertThat} that does nothing. */
  private static final AssertThat DISABLED =
      new AssertThat((Config) null) {
        @Override
        public AssertThat with(Config config) {
          return this;
        }

        @Override
        public AssertThat with(ConnectionFactory connectionFactory) {
          return this;
        }

        @Override
        public AssertThat with(Map<String, String> map) {
          return this;
        }

        @Override
        public AssertThat with(String name, Object schema) {
          return this;
        }

        @Override
        public AssertThat withModel(String model) {
          return this;
        }

        @Override
        public AssertQuery query(String sql) {
          return NopAssertQuery.of(sql);
        }

        @Override
        public void connectThrows(String message) {
          // nothing
        }

        @Override
        public void connectThrows(Function1<Throwable, Void> exceptionChecker) {
          // nothing
        }

        @Override
        public <T> AssertThat doWithConnection(Function1<OptiqConnection, T> fn)
          throws Exception {
          return this;
        }

        @Override
        public AssertThat withSchema(String schema) {
          return this;
        }

        @Override
        public AssertThat enable(boolean enabled) {
          return this;
        }
      };

  /** Creates an instance of {@code OptiqAssert} with the regular
   * configuration. */
  public static AssertThat that() {
    return new AssertThat(Config.REGULAR);
  }

  static Function1<Throwable, Void> checkException(
      final String expected) {
    return new Function1<Throwable, Void>() {
      public Void apply(Throwable p0) {
        assertNotNull(
            "expected exception but none was thrown", p0);
        StringWriter stringWriter = new StringWriter();
        PrintWriter printWriter = new PrintWriter(stringWriter);
        p0.printStackTrace(printWriter);
        printWriter.flush();
        String stack = stringWriter.toString();
        assertTrue(stack, stack.contains(expected));
        return null;
      }
    };
  }

  static Function1<ResultSet, Void> checkResult(final String expected) {
    return new Function1<ResultSet, Void>() {
      public Void apply(ResultSet resultSet) {
        try {
          final String resultString = OptiqAssert.toString(resultSet);
          assertEquals(expected, Util.toLinux(resultString));
          return null;
        } catch (SQLException e) {
          throw new RuntimeException(e);
        }
      }
    };
  }

  static Function1<ResultSet, Void> checkResultCount(final int expected) {
    return new Function1<ResultSet, Void>() {
      public Void apply(ResultSet resultSet) {
        try {
          final int count = OptiqAssert.countRows(resultSet);
          assertEquals(expected, count);
          return null;
        } catch (SQLException e) {
          throw new RuntimeException(e);
        }
      }
    };
  }

  /** Checks that the result of the second and subsequent executions is the same
   * as the first.
   *
   * @param ordered Whether order should be the same both times
   */
  static Function1<ResultSet, Void> consistentResult(final boolean ordered) {
    return new Function1<ResultSet, Void>() {
      int executeCount = 0;
      Collection expected;

      public Void apply(ResultSet resultSet) {
        ++executeCount;
        try {
          final Collection result =
              OptiqAssert.toStringList(resultSet,
                  ordered ? new ArrayList<String>() : new TreeSet<String>());
          if (executeCount == 1) {
            expected = result;
          } else {
            if (!expected.equals(result)) {
              // compare strings to get better error message
              assertThat(newlineList(result), equalTo(newlineList(expected)));
              fail("oops");
            }
          }
          return null;
        } catch (SQLException e) {
          throw new RuntimeException(e);
        }
      }
    };
  }

  static String newlineList(Collection collection) {
    final StringBuilder buf = new StringBuilder();
    for (Object o : collection) {
      buf.append(o).append('\n');
    }
    return buf.toString();
  }

  static Function1<ResultSet, Void> checkResultUnordered(
      final String... lines) {
    return new Function1<ResultSet, Void>() {
      public Void apply(ResultSet resultSet) {
        try {
          final List<String> expectedList = new ArrayList<String>();
          Collections.addAll(expectedList, lines);
          Collections.sort(expectedList);

          final List<String> actualList = new ArrayList<String>();
          OptiqAssert.toStringList(resultSet, actualList);
          Collections.sort(actualList);

          // Use assertArrayEquals since it implements fine-grained comparison.
          assertArrayEquals(expectedList.toArray(), actualList.toArray());
          return null;
        } catch (SQLException e) {
          throw new RuntimeException(e);
        }
      }
    };
  }

  public static Function1<ResultSet, Void> checkResultContains(
      final String expected) {
    return new Function1<ResultSet, Void>() {
      public Void apply(ResultSet s) {
        try {
          final String actual = Util.toLinux(OptiqAssert.toString(s));
          if (!actual.contains(expected)) {
            assertEquals("contains", expected, actual);
          }
          return null;
        } catch (SQLException e) {
          throw new RuntimeException(e);
        }
      }
    };
  }

  public static Function1<ResultSet, Void> checkResultType(
      final String expected) {
    return new Function1<ResultSet, Void>() {
      public Void apply(ResultSet s) {
        try {
          final String actual = typeString(s.getMetaData());
          assertEquals(expected, actual);
          return null;
        } catch (SQLException e) {
          throw new RuntimeException(e);
        }
      }
    };
  }

  private static String typeString(ResultSetMetaData metaData)
    throws SQLException {
    final List<String> list = new ArrayList<String>();
    for (int i = 0; i < metaData.getColumnCount(); i++) {
      list.add(
          metaData.getColumnName(i + 1)
              + " "
              + metaData.getColumnTypeName(i + 1)
              + (metaData.isNullable(i + 1) == ResultSetMetaData.columnNoNulls
              ? " NOT NULL"
              : ""));
    }
    return list.toString();
  }

  static void assertQuery(
      Connection connection,
      String sql,
      int limit,
      boolean materializationsEnabled,
      Function1<ResultSet, Void> resultChecker,
      Function1<Throwable, Void> exceptionChecker) throws Exception {
    final String message =
        "With materializationsEnabled=" + materializationsEnabled
        + ", limit=" + limit;
    try {
      ((OptiqConnection) connection).getProperties().setProperty(
          "materializationsEnabled", Boolean.toString(materializationsEnabled));
      Statement statement = connection.createStatement();
      statement.setMaxRows(limit <= 0 ? limit : Math.max(limit, 1));
      ResultSet resultSet;
      try {
        resultSet = statement.executeQuery(sql);
        if (exceptionChecker != null) {
          exceptionChecker.apply(null);
          return;
        }
      } catch (Exception e) {
        if (exceptionChecker != null) {
          exceptionChecker.apply(e);
          return;
        }
        throw e;
      } catch (Error e) {
        if (exceptionChecker != null) {
          exceptionChecker.apply(e);
          return;
        }
        throw e;
      }
      if (resultChecker != null) {
        resultChecker.apply(resultSet);
      }
      resultSet.close();
      statement.close();
      connection.close();
    } catch (Throwable e) {
      throw new RuntimeException(message, e);
    }
  }

  static void assertPrepare(
      Connection connection,
      String sql,
      boolean materializationsEnabled,
      final Function1<RelNode, Void> convertChecker) throws Exception {
    final String message =
        "With materializationsEnabled=" + materializationsEnabled;
    Hook.Closeable closeable = Hook.TRIMMED.addThread(
        new Function1<Object, Object>() {
          public Object apply(Object a0) {
            RelNode rel = (RelNode) a0;
            convertChecker.apply(rel);
            return null;
          }
        });
    try {
      ((OptiqConnection) connection).getProperties().setProperty(
          "materializationsEnabled", Boolean.toString(materializationsEnabled));
      PreparedStatement statement = connection.prepareStatement(sql);
      statement.close();
      connection.close();
    } catch (Throwable e) {
      throw new RuntimeException(message, e);
    } finally {
      closeable.close();
    }
  }

  static String toString(ResultSet resultSet) throws SQLException {
    final StringBuilder buf = new StringBuilder();
    final ResultSetMetaData metaData = resultSet.getMetaData();
    while (resultSet.next()) {
      int n = metaData.getColumnCount();
      if (n > 0) {
        for (int i = 1;; i++) {
          buf.append(metaData.getColumnLabel(i))
              .append("=")
              .append(resultSet.getString(i));
          if (i == n) {
            break;
          }
          buf.append("; ");
        }
      }
      buf.append("\n");
    }
    return buf.toString();
  }

  static int countRows(ResultSet resultSet) throws SQLException {
    int n = 0;
    while (resultSet.next()) {
      ++n;
    }
    return n;
  }

  static Collection<String> toStringList(ResultSet resultSet,
      Collection<String> list) throws SQLException {
    final StringBuilder buf = new StringBuilder();
    while (resultSet.next()) {
      int n = resultSet.getMetaData().getColumnCount();
      if (n > 0) {
        for (int i = 1;; i++) {
          buf.append(resultSet.getMetaData().getColumnLabel(i))
              .append("=")
              .append(resultSet.getString(i));
          if (i == n) {
            break;
          }
          buf.append("; ");
        }
      }
      list.add(buf.toString());
      buf.setLength(0);
    }
    return list;
  }

  static ImmutableMultiset<String> toSet(ResultSet resultSet)
    throws SQLException {
    return ImmutableMultiset.copyOf(
        toStringList(resultSet, new ArrayList<String>()));
  }

  /** Calls a non-static method via reflection. Useful for testing methods that
   * don't exist in certain versions of the JDK. */
  static Object call(Object o, String methodName, Object... args)
    throws NoSuchMethodException, InvocationTargetException,
      IllegalAccessException {
    return method(o, methodName, args).invoke(o, args);
  }

  /** Finds a non-static method based on its target, name and arguments.
   * Throws if not found. */
  static Method method(Object o, String methodName, Object[] args) {
    for (Class<?> aClass = o.getClass();;) {
    loop:
      for (Method method1 : aClass.getMethods()) {
        if (method1.getName().equals(methodName)
            && method1.getParameterTypes().length == args.length
            && Modifier.isPublic(method1.getDeclaringClass().getModifiers())) {
          for (Pair<Object, Class<?>> pair
              : Pair.zip(args, method1.getParameterTypes())) {
            if (!pair.right.isInstance(pair.left)) {
              continue loop;
            }
          }
          return method1;
        }
      }
      if (aClass.getSuperclass() != null
          && aClass.getSuperclass() != Object.class) {
        aClass = aClass.getSuperclass();
      } else {
        final Class<?>[] interfaces = aClass.getInterfaces();
        if (interfaces.length > 0) {
          aClass = interfaces[0];
        } else {
          break;
        }
      }
    }
    throw new AssertionError("method " + methodName + " not found");
  }

  static OptiqConnection getConnection(String... schema)
    throws ClassNotFoundException, SQLException {
    final List<String> schemaList = Arrays.asList(schema);
    Class.forName("net.hydromatic.optiq.jdbc.Driver");
    String suffix = schemaList.contains("spark") ? "spark=true" : "";
    Connection connection =
        DriverManager.getConnection("jdbc:optiq:" + suffix);
    OptiqConnection optiqConnection =
        connection.unwrap(OptiqConnection.class);
    SchemaPlus rootSchema = optiqConnection.getRootSchema();
    if (schemaList.contains("hr")) {
      rootSchema.add("hr", new ReflectiveSchema(new JdbcTest.HrSchema()));
    }
    if (schemaList.contains("foodmart")) {
      rootSchema.add("foodmart",
          new ReflectiveSchema(new JdbcTest.FoodmartSchema()));
    }
    if (schemaList.contains("lingual")) {
      rootSchema.add("SALES",
          new ReflectiveSchema(new JdbcTest.LingualSchema()));
    }
    if (schemaList.contains("post")) {
      final SchemaPlus post = rootSchema.add("POST", new AbstractSchema());
      post.add("EMP",
          ViewTable.viewMacro(
              post,
              "select * from (values\n"
              + "    ('Jane', 10, 'F'),\n"
              + "    ('Bob', 10, 'M'),\n"
              + "    ('Eric', 20, 'M'),\n"
              + "    ('Susan', 30, 'F'),\n"
              + "    ('Alice', 30, 'F'),\n"
              + "    ('Adam', 50, 'M'),\n"
              + "    ('Eve', 50, 'F'),\n"
              + "    ('Grace', 60, 'F'),\n"
              + "    ('Wilma', cast(null as integer), 'F'))\n"
              + "  as t(ename, deptno, gender)",
              ImmutableList.<String>of()));
      post.add("DEPT",
          ViewTable.viewMacro(
              post,
              "select * from (values\n"
              + "    (10, 'Sales'),\n"
              + "    (20, 'Marketing'),\n"
              + "    (30, 'Engineering'),\n"
              + "    (40, 'Empty')) as t(deptno, dname)",
              ImmutableList.<String>of()));
      post.add("EMPS",
          ViewTable.viewMacro(
              post,
              "select * from (values\n"
              + "    (100, 'Fred',  10, CAST(NULL AS CHAR(1)), CAST(NULL AS VARCHAR(20)), 40,               25, TRUE,    FALSE, DATE '1996-08-03'),\n"
              + "    (110, 'Eric',  20, 'M',                   'San Francisco',           3,                80, UNKNOWN, FALSE, DATE '2001-01-01'),\n"
              + "    (110, 'John',  40, 'M',                   'Vancouver',               2, CAST(NULL AS INT), FALSE,   TRUE,  DATE '2002-05-03'),\n"
              + "    (120, 'Wilma', 20, 'F',                   CAST(NULL AS VARCHAR(20)), 1,                 5, UNKNOWN, TRUE,  DATE '2005-09-07'),\n"
              + "    (130, 'Alice', 40, 'F',                   'Vancouver',               2, CAST(NULL AS INT), FALSE,   TRUE,  DATE '2007-01-01'))\n"
              + " as t(empno, name, deptno, gender, city, empid, age, slacker, manager, joinedat)",
              ImmutableList.<String>of()));
    }
    if (schemaList.contains("metadata")) {
      // always present
      Util.discard(0);
    }
    return optiqConnection;
  }

  /**
   * Creates a connection with a given query provider. If provider is null,
   * uses the connection as its own provider. The connection contains a
   * schema called "foodmart" backed by a JDBC connection to MySQL.
   *
   * @param withClone Whether to create a "foodmart2" schema as in-memory
   *     clone
   * @return Connection
   * @throws ClassNotFoundException
   * @throws java.sql.SQLException
   */
  static OptiqConnection getConnection(boolean withClone)
    throws ClassNotFoundException, SQLException {
    Class.forName("net.hydromatic.optiq.jdbc.Driver");
    Connection connection = DriverManager.getConnection("jdbc:optiq:");
    OptiqConnection optiqConnection =
        connection.unwrap(OptiqConnection.class);
    final SchemaPlus rootSchema = optiqConnection.getRootSchema();
    final DataSource dataSource =
        JdbcSchema.dataSource(
            CONNECTION_SPEC.url,
            CONNECTION_SPEC.driver,
            CONNECTION_SPEC.username,
            CONNECTION_SPEC.password);
    final SchemaPlus foodmart =
        rootSchema.add("foodmart",
            JdbcSchema.create(rootSchema, "foodmart", dataSource, null,
                "foodmart"));
    if (withClone) {
      rootSchema.add("foodmart2", new CloneSchema(foodmart));
    }
    optiqConnection.setSchema("foodmart2");
    return optiqConnection;
  }

  /**
   * Result of calling {@link OptiqAssert#that}.
   */
  public static class AssertThat {
    private final ConnectionFactory connectionFactory;

    private AssertThat(Config config) {
      this(new ConfigConnectionFactory(config));
    }

    private AssertThat(ConnectionFactory connectionFactory) {
      this.connectionFactory = connectionFactory;
    }

    public AssertThat with(Config config) {
      return new AssertThat(config);
    }

    public AssertThat with(ConnectionFactory connectionFactory) {
      return new AssertThat(connectionFactory);
    }

    public AssertThat with(final Map<String, String> map) {
      return new AssertThat(
          new ConnectionFactory() {
            public OptiqConnection createConnection() throws Exception {
              Class.forName("net.hydromatic.optiq.jdbc.Driver");
              final Properties info = new Properties();
              for (Map.Entry<String, String> entry : map.entrySet()) {
                info.setProperty(entry.getKey(), entry.getValue());
              }
              return (OptiqConnection) DriverManager.getConnection(
                  "jdbc:optiq:", info);
            }
          });
    }

    /** Sets the default schema to a reflective schema based on a given
     * object. */
    public AssertThat with(final String name, final Object schema) {
      return with(
          new OptiqAssert.ConnectionFactory() {
            public OptiqConnection createConnection() throws Exception {
              Class.forName("net.hydromatic.optiq.jdbc.Driver");
              Connection connection =
                  DriverManager.getConnection("jdbc:optiq:");
              OptiqConnection optiqConnection =
                  connection.unwrap(OptiqConnection.class);
              SchemaPlus rootSchema =
                  optiqConnection.getRootSchema();
              rootSchema.add(name, new ReflectiveSchema(schema));
              optiqConnection.setSchema(name);
              return optiqConnection;
            }
          });
    }

    public AssertThat withModel(final String model) {
      return new AssertThat(
          new OptiqAssert.ConnectionFactory() {
            public OptiqConnection createConnection() throws Exception {
              Class.forName("net.hydromatic.optiq.jdbc.Driver");
              final Properties info = new Properties();
              info.setProperty("model", "inline:" + model);
              return (OptiqConnection) DriverManager.getConnection(
                  "jdbc:optiq:", info);
            }
          });
    }

    /** Adds materializations to the schema. */
    public AssertThat withMaterializations(
        String model, String... materializations) {
      assert materializations.length % 2 == 0;
      final JsonBuilder builder = new JsonBuilder();
      final List<Object> list = builder.list();
      for (int i = 0; i < materializations.length; i++) {
        String table = materializations[i++];
        final Map<String, Object> map = builder.map();
        map.put("table", table);
        map.put("view", table + "v");
        String sql = materializations[i];
        final String sql2 = sql
            .replaceAll("`", "\"");
        map.put("sql", sql2);
        list.add(map);
      }
      final String buf =
          "materializations: " + builder.toJsonString(list);
      final String model2;
      if (model.contains("defaultSchema: 'foodmart'")) {
        model2 = model.replace("]",
            ", { name: 'mat', "
            + buf
            + "}\n"
            + "]");
      } else if (model.contains("type: ")) {
        model2 = model.replace("type: ",
            buf + ",\n"
            + "type: ");
      } else {
        throw new AssertionError("do not know where to splice");
      }
      return withModel(model2);
    }

    public AssertQuery query(String sql) {
      return new AssertQuery(connectionFactory, sql);
    }

    /** Asserts that there is an exception with the given message while
     * creating a connection. */
    public void connectThrows(String message) {
      connectThrows(checkException(message));
    }

    /** Asserts that there is an exception that matches the given predicate
     * while creating a connection. */
    public void connectThrows(
        Function1<Throwable, Void> exceptionChecker) {
      Throwable throwable;
      try {
        Connection x = connectionFactory.createConnection();
        try {
          x.close();
        } catch (SQLException e) {
          // ignore
        }
        throwable = null;
      } catch (Throwable e) {
        throwable = e;
      }
      exceptionChecker.apply(throwable);
    }

    /** Creates a {@link OptiqConnection} and executes a callback. */
    public <T> AssertThat doWithConnection(Function1<OptiqConnection, T> fn)
      throws Exception {
      Connection connection = connectionFactory.createConnection();
      try {
        T t = fn.apply((OptiqConnection) connection);
        Util.discard(t);
        return AssertThat.this;
      } finally {
        connection.close();
      }
    }

    /** Creates a {@link DataContext} and executes a callback. */
    public <T> AssertThat doWithDataContext(Function1<DataContext, T> fn)
      throws Exception {
      OptiqConnection connection = connectionFactory.createConnection();
      final DataContext dataContext = MetaImpl.createDataContext(connection);
      try {
        T t = fn.apply(dataContext);
        Util.discard(t);
        return AssertThat.this;
      } finally {
        connection.close();
      }
    }

    public AssertThat withSchema(String schema) {
      return new AssertThat(
          new SchemaConnectionFactory(connectionFactory, schema));
    }

    /** Use sparingly. Does not close the connection. */
    public Connection connect() throws Exception {
      return connectionFactory.createConnection();
    }

    public AssertThat enable(boolean enabled) {
      return enabled ? this : DISABLED;
    }
  }

  public interface ConnectionFactory {
    OptiqConnection createConnection() throws Exception;
  }

  private static class ConfigConnectionFactory implements ConnectionFactory {
    private final Config config;

    public ConfigConnectionFactory(Config config) {
      this.config = config;
    }

    public OptiqConnection createConnection() throws Exception {
      switch (config) {
      case REGULAR:
        return getConnection("hr", "foodmart", "post");
      case REGULAR_PLUS_METADATA:
        return getConnection("hr", "foodmart", "metadata");
      case LINGUAL:
        return getConnection("lingual");
      case JDBC_FOODMART:
        return getConnection(false);
      case FOODMART_CLONE:
        return getConnection(true);
      case SPARK:
        return getConnection("spark");
      default:
        throw Util.unexpected(config);
      }
    }
  }

  private static class DelegatingConnectionFactory
      implements ConnectionFactory {
    private final ConnectionFactory factory;

    public DelegatingConnectionFactory(ConnectionFactory factory) {
      this.factory = factory;
    }

    public OptiqConnection createConnection() throws Exception {
      return factory.createConnection();
    }
  }

  private static class SchemaConnectionFactory
      extends DelegatingConnectionFactory {
    private final String schema;

    public SchemaConnectionFactory(ConnectionFactory factory, String schema) {
      super(factory);
      this.schema = schema;
    }

    @Override
    public OptiqConnection createConnection() throws Exception {
      OptiqConnection connection = super.createConnection();
      connection.setSchema(schema);
      return connection;
    }
  }

  public static class AssertQuery {
    private final String sql;
    private ConnectionFactory connectionFactory;
    private String plan;
    private int limit;
    private boolean materializationsEnabled = false;

    private AssertQuery(ConnectionFactory connectionFactory, String sql) {
      this.sql = sql;
      this.connectionFactory = connectionFactory;
    }

    protected Connection createConnection() throws Exception {
      return connectionFactory.createConnection();
    }

    public AssertQuery enable(boolean enabled) {
      return enabled ? this : NopAssertQuery.of(sql);
    }

    public AssertQuery returns(String expected) {
      return returns(checkResult(expected));
    }

    public AssertQuery returnsCount(int expectedCount) {
      return returns(checkResultCount(expectedCount));
    }

    public AssertQuery returns(Function1<ResultSet, Void> checker) {
      try {
        assertQuery(createConnection(), sql, limit, materializationsEnabled,
            checker, null);
        return this;
      } catch (Exception e) {
        throw new RuntimeException(
            "exception while executing [" + sql + "]", e);
      }
    }

    public AssertQuery returnsUnordered(String... lines) {
      return returns(checkResultUnordered(lines));
    }

    public AssertQuery throws_(String message) {
      try {
        assertQuery(createConnection(), sql, limit, materializationsEnabled,
            null, checkException(message));
        return this;
      } catch (Exception e) {
        throw new RuntimeException(
            "exception while executing [" + sql + "]", e);
      }
    }

    public AssertQuery runs() {
      try {
        assertQuery(createConnection(), sql, limit, materializationsEnabled,
            null, null);
        return this;
      } catch (Exception e) {
        throw new RuntimeException(
            "exception while executing [" + sql + "]", e);
      }
    }

    public AssertQuery typeIs(String expected) {
      try {
        assertQuery(
            createConnection(), sql, limit, false,
            checkResultType(expected), null);
        return this;
      } catch (Exception e) {
        throw new RuntimeException(
            "exception while executing [" + sql + "]", e);
      }
    }

    /** Checks that when the query (which was set using
     * {@link AssertThat#query(String)}) is converted to a relational algebra
     * expression matching the given string. */
    public AssertQuery convertContains(final String expected) {
      return convertMatches(
          new Function1<RelNode, Void>() {
            public Void apply(RelNode relNode) {
              String s = RelOptUtil.toString(relNode);
              assertThat(s, containsString(expected));
              return null;
            }
          });
    }

    public AssertQuery convertMatches(final Function1<RelNode, Void> checker) {
      try {
        assertPrepare(createConnection(), sql, false, checker);
        return this;
      } catch (Exception e) {
        throw new RuntimeException(
            "exception while preparing [" + sql + "]", e);
      }
    }

    public AssertQuery explainContains(String expected) {
      return explainMatches(checkResultContains(expected));
    }

    public AssertQuery explainMatches(Function1<ResultSet, Void> checker) {
      String explainSql = "explain plan for " + sql;
      try {
        assertQuery(
            createConnection(), explainSql, limit, materializationsEnabled,
            checker, null);
        return this;
      } catch (Exception e) {
        throw new RuntimeException(
            "exception while executing [" + explainSql + "]", e);
      }
    }

    public AssertQuery planContains(String expected) {
      ensurePlan();
      assertTrue(
          "Plan [" + plan + "] contains [" + expected + "]",
          Util.toLinux(plan)
              .replaceAll("\\\\r\\\\n", "\\\\n")
              .contains(expected));
      return this;
    }

    public AssertQuery planHasSql(String expected) {
      return planContains(
          "getDataSource(), \""
          + expected.replace("\\", "\\\\")
              .replace("\"", "\\\"")
              .replaceAll("\n", "\\\\n")
          + "\"");
    }

    private void ensurePlan() {
      if (plan != null) {
        return;
      }
      final Hook.Closeable hook = Hook.JAVA_PLAN.addThread(
          new Function1<Object, Object>() {
            public Object apply(Object a0) {
              plan = (String) a0;
              return null;
            }
          });
      try {
        assertQuery(createConnection(), sql, limit, materializationsEnabled,
            null, null);
        assertNotNull(plan);
      } catch (Exception e) {
        throw new RuntimeException(
            "exception while executing [" + sql + "]", e);
      } finally {
        hook.close();
      }
    }

    /** Runs the query and applies a checker to the generated third-party
     * queries. The checker should throw to fail the test if it does not see
     * what it wants. This method can be used to check whether a particular
     * MongoDB or SQL query is generated, for instance. */
    public AssertQuery queryContains(Function1<List, Void> predicate1) {
      final List<Object> list = new ArrayList<Object>();
      final Hook.Closeable hook = Hook.QUERY_PLAN.addThread(
          new Function1<Object, Object>() {
            public Object apply(Object a0) {
              list.add(a0);
              return null;
            }
          });
      try {
        assertQuery(createConnection(), sql, limit, materializationsEnabled,
            null, null);
        predicate1.apply(list);
        return this;
      } catch (Exception e) {
        throw new RuntimeException(
            "exception while executing [" + sql + "]", e);
      } finally {
        hook.close();
      }
    }

    /** Sets a limit on the number of rows returned. -1 means no limit. */
    public AssertQuery limit(int limit) {
      this.limit = limit;
      return this;
    }

    public void sameResultWithMaterializationsDisabled() {
      boolean save = materializationsEnabled;
      try {
        materializationsEnabled = false;
        final boolean ordered = sql.toUpperCase().contains("ORDER BY");
        final Function1<ResultSet, Void> checker = consistentResult(ordered);
        returns(checker);
        materializationsEnabled = true;
        returns(checker);
      } finally {
        materializationsEnabled = save;
      }
    }

    public AssertQuery enableMaterializations(boolean enable) {
      this.materializationsEnabled = enable;
      return this;
    }
  }

  public enum Config {
    /**
     * Configuration that creates a connection with two in-memory data sets:
     * {@link net.hydromatic.optiq.test.JdbcTest.HrSchema} and
     * {@link net.hydromatic.optiq.test.JdbcTest.FoodmartSchema}.
     */
    REGULAR,

    /**
     * Configuration that creates a connection with an in-memory data set
     * similar to the smoke test in Cascading Lingual.
     */
    LINGUAL,

    /**
     * Configuration that creates a connection to a MySQL server. Tables
     * such as "customer" and "sales_fact_1997" are available. Queries
     * are processed by generating Java that calls linq4j operators
     * such as
     * {@link net.hydromatic.linq4j.Enumerable#where(net.hydromatic.linq4j.function.Predicate1)}.
     */
    JDBC_FOODMART,

    /** Configuration that contains an in-memory clone of the FoodMart
     * database. */
    FOODMART_CLONE,

    /** Configuration that includes the metadata schema. */
    REGULAR_PLUS_METADATA,

    /** Configuration that loads Spark. */
    SPARK,
  }

  /** Implementation of {@link AssertQuery} that does nothing. */
  private static class NopAssertQuery extends AssertQuery {
    private NopAssertQuery(String sql) {
      super(null, sql);
    }

    /** Returns an implementation of {@link AssertQuery} that does nothing. */
    static AssertQuery of(final String sql) {
      return new NopAssertQuery(sql);
    }

    @Override
    protected Connection createConnection() throws Exception {
      throw new AssertionError("disabled");
    }

    @Override
    public AssertQuery returns(String expected) {
      return this;
    }

    @Override
    public AssertQuery returns(Function1<ResultSet, Void> checker) {
      return this;
    }

    @Override
    public AssertQuery throws_(String message) {
      return this;
    }

    @Override
    public AssertQuery runs() {
      return this;
    }

    @Override public AssertQuery convertContains(String expected) {
      return this;
    }

    @Override
    public AssertQuery explainMatches(Function1<ResultSet, Void> checker) {
      return this;
    }

    @Override
    public AssertQuery planContains(String expected) {
      return this;
    }

    @Override
    public AssertQuery planHasSql(String expected) {
      return this;
    }

    @Override
    public AssertQuery queryContains(Function1<List, Void> predicate1) {
      return this;
    }
  }

  /** Information necessary to create a JDBC connection. Specify one to run
   * tests against a different database. (hsqldb is the default.) */
  public enum ConnectionSpec {
    HSQLDB("jdbc:hsqldb:res:foodmart", "FOODMART", "FOODMART",
        "org.hsqldb.jdbcDriver"),
    MYSQL("jdbc:mysql://localhost/foodmart", "foodmart", "foodmart",
        "com.mysql.jdbc.Driver");

    public final String url;
    public final String username;
    public final String password;
    public final String driver;

    ConnectionSpec(String url, String username, String password,
        String driver) {
      this.url = url;
      this.username = username;
      this.password = password;
      this.driver = driver;
    }
  }
}

// End OptiqAssert.java
TOP

Related Classes of net.hydromatic.optiq.test.OptiqAssert$AssertQuery

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.