Статьи

SQL поверх всего с помощью адаптера Optiq

В этом посте показано, как создать адаптер Optiq. Существуют примеры с optiq-csv и несколькими другими проектами, но я нашел их немного сложными для понимания. Более того, функция опускания запросов не очень хорошо документирована, и я скоро сделаю последующий пост.

Что такое Optiq?
Optiq ( https://github.com/julianhyde/optiq ) — это механизм планирования запросов, который может помочь вам выполнять и планировать SQL для ваших источников данных. Например, есть проект optiq-web, с помощью которого вы можете указывать на вики-страницы с таблицами и запрашивать эти HTML-таблицы через SQL. Существуют аналогичные проекты для запуска SQL на CSV, JSON-файлах, MongoDB и т. Д. Допустим, вы создали собственное хранилище данных и хотите предоставить SQL-доступ к нему, тогда Optiq — хороший выбор. Вам просто нужно написать адаптер Optiq для вашего источника данных.

Пример использования В
любом случае, целью этой публикации является пошаговое руководство по написанию собственного адаптера. Давайте посмотрим, как я написал адаптер для запуска SQL поверх объектов JavaBean. Допустим, у вас есть список пользователей (объекты JavaBean), и вы хотели бы выполнять запросы типа: «выбрать максимум (возраст) из пользователей» и т. Д. Optiq имеет встроенную ReflectiveSchema, которую можно использовать здесь, но давайте сделаем нашу собственную реализацию, чтобы увидеть как это сделано, также ReflectiveSchema не имеет запроса вниз, который я планирую добавить.

Создание
ссылки на исходный код учебника по адаптеруhttps://github.com/cyberabis/optiq-javabean

Шаг 1. Создание класса схемы Класс
схемы является эквивалентом базы данных и может содержать несколько таблиц.
Расширьте AbstractSchema и переопределите метод getTableMap. Этот метод должен возвращать имена таблиц и таблиц. Как создать класс таблицы является следующим.

package io.thedal.optiq.javabean;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableMap;

import net.hydromatic.optiq.Table;
import net.hydromatic.optiq.impl.AbstractSchema;

/**
* JavaBeanSchema is a type of Optiq Schema that contains a list of tables. A
* table is a List of JavaBean Objects of the same type.
*
* @author Abishek Baskaran
*/
public class JavaBeanSchema extends AbstractSchema {

  static final Logger logger = LoggerFactory.getLogger(JavaBeanSchema.class);
  private String schemaName;
  private Map<String, List> javaBeanListMap = new HashMap<String, List>();

  /**
* Constructor
*
* @param schemaName
* The schema name which is like database name.
*/
  public JavaBeanSchema(String schemaName) {
    super();
    this.schemaName = schemaName;
  }

  /**
* Adds a table to the schema.
*
* @param tableName
* The name of the table, has to be unique else will overwrite.
* @param javaBeanList
* A List of JavaBeans of same type that's to be seen as table.
*/
  public <E> void addAsTable(String tableName, List<E> javaBeanList) {
    javaBeanListMap.put(tableName, javaBeanList);
    logger.info("Added table: " + tableName + " to Schema: " + schemaName);
  }

  /**
* @return The name of the schema
*/
  public String getName() {
    return schemaName;
  }

  @Override
  protected Map<String, Table> getTableMap() {
    final ImmutableMap.Builder<String, Table> builder = ImmutableMap.builder();
    for (String tableName : javaBeanListMap.keySet()) {
      Table javaBeanTable = new JavaBeanTable(javaBeanListMap.get(tableName));
      builder.put(tableName, javaBeanTable);
      logger.debug("Initialized JavaBeanTable for: " + tableName);
    }
    return builder.build();
  }

}

Шаг 2. Создание класса
таблицы. Создание класса таблицы, расширяющего AbstractQueryableTable и реализующего TranslatableTable.
Таблица имеет 2 важных метода:
getRowType — этот метод должен возвращать заголовки строк таблицы и их типы в двух массивах, таких как [Name, Age, Country] и [String, Integer, String].
asQueryable — этот метод должен возвращать перечислитель. Перечислитель будет иметь методы для итерации фактических строк в таблице. Ниже описано, как создать перечислитель.

package io.thedal.optiq.javabean;

import io.thedal.optiq.javabean.utils.JavaBeanInspector;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.eigenbase.rel.RelNode;
import org.eigenbase.relopt.RelOptTable;
import org.eigenbase.relopt.RelOptTable.ToRelContext;
import org.eigenbase.reltype.RelDataType;
import org.eigenbase.reltype.RelDataTypeFactory;
import org.eigenbase.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.hydromatic.linq4j.Enumerator;
import net.hydromatic.linq4j.QueryProvider;
import net.hydromatic.linq4j.Queryable;
import net.hydromatic.optiq.SchemaPlus;
import net.hydromatic.optiq.TranslatableTable;
import net.hydromatic.optiq.impl.AbstractTableQueryable;
import net.hydromatic.optiq.impl.java.AbstractQueryableTable;
import net.hydromatic.optiq.rules.java.EnumerableConvention;
import net.hydromatic.optiq.rules.java.JavaRules;

/**
* JavaBeanTable is an Optiq table that accepts a List of JavaBeans. JavaBeans
* can have only fields of eligible type / class defined in
* JavaBeanInspector.checkMethodEligibility.
*
* @author Abishek Baskaran
*
* @param <E>
* Table contains items for a specific Class E
*/
public class JavaBeanTable<E> extends AbstractQueryableTable implements
    TranslatableTable {

  static final Logger logger = LoggerFactory.getLogger(JavaBeanTable.class);
  private List<E> javaBeanList;

  /**
* Constructor
*
* @param javaBeanList
* A JavaBean List
*/
  public JavaBeanTable(List<E> javaBeanList) {
    super(Object[].class);
    this.javaBeanList = javaBeanList;
  }

  @Override
  public RelDataType getRowType(RelDataTypeFactory typeFactory) {
    List<String> names = new ArrayList<String>();
    List<RelDataType> types = new ArrayList<RelDataType>();
    if ((javaBeanList != null) && (javaBeanList.size() > 0)) {
      Class sample = javaBeanList.get(0).getClass();
      Method[] methods = sample.getMethods();
      for (Method method : methods) {
        if (JavaBeanInspector.checkMethodEligiblity(method)) {
          String name = method.getName().substring(3);
          Class type = method.getReturnType();
          names.add(name);
          types.add(typeFactory.createJavaType(type));
          logger.info("Added field name: " + name + " of type: "
              + type.getSimpleName());
        }
      }
    }
    return typeFactory.createStructType(Pair.zip(names, types));
  }

  @Override
  public <T> Queryable<T> asQueryable(QueryProvider queryProvider,
      SchemaPlus schema, String tableName) {
    logger.info("Got query request for: " + tableName);
    return new AbstractTableQueryable<T>(queryProvider, schema, this, tableName) {
      public Enumerator<T> enumerator() {
        // noinspection unchecked
        try {
          JavaBeanEnumerator enumerator = new JavaBeanEnumerator(javaBeanList);
          return (Enumerator<T>) enumerator;
        } catch (Exception e) {
          throw new RuntimeException(e);
        }
      }
    };
  }

  @Override
  public RelNode toRel(ToRelContext context, RelOptTable relOptTable) {
    return new JavaRules.EnumerableTableAccessRel(context.getCluster(), context
        .getCluster().traitSetOf(EnumerableConvention.INSTANCE), relOptTable,
        (Class) getElementType());
  }

}

Шаг 3. Создание класса-перечислителя.
У перечислителя должен быть итератор для строк в таблице. Строки в таблице смоделированы как объект []. Поэтому мы должны преобразовать наш пользовательский компонент хранения в итератор. В моем случае мой компонент хранения — это список JavaBean — я конвертировал его в итератор в своем конструкторе Enumerator. Внедренные методы являются информативными.

package io.thedal.optiq.javabean;

import io.thedal.optiq.javabean.utils.JavaBeanInspector;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.hydromatic.linq4j.Enumerator;

/**
* JavaBeanEnumerator converts a JavaBean List into rows. A Row is an Object
* array of columns. The iterator over every row is called rows.
*
* @author Abishek Baskaran
*
*/
public class JavaBeanEnumerator implements Enumerator<Object> {

  static final Logger logger = LoggerFactory
      .getLogger(JavaBeanEnumerator.class);
  private Object current;
  private Iterator<Object[]> rowIterator;

  /**
* Constructor - forms the row iterator.
*
* @param javaBeanList
*/
  public <E> JavaBeanEnumerator(List<E> javaBeanList) {
    List<Object[]> rows = new ArrayList<Object[]>();
    for (Object javaBean : javaBeanList) {
      rows.add(getRow(javaBean));
    }
    rowIterator = rows.iterator();
    logger.debug("Created an iterator for the enumerator");
  }

  private Object[] getRow(Object javaBean) {
    List<Object> row = new ArrayList<Object>();
    Class clazz = javaBean.getClass();
    Method[] methods = clazz.getMethods();
    for (Method method : methods) {
      if (JavaBeanInspector.checkMethodEligiblity(method)) {
        try {
          row.add(method.invoke(javaBean));
        } catch (IllegalAccessException e) {
          logger.error("Unable to invoke method via reflection");
        } catch (IllegalArgumentException e) {
          logger.error("Unable to invoke method via reflection");
        } catch (InvocationTargetException e) {
          logger.error("Unable to invoke method via reflection");
        }
      }
    }
    logger.debug("Formed row is: " + row);
    return row.toArray();
  }

  @Override
  public void close() {
    // Nothing to do
  }

  @Override
  public Object current() {
    if (current == null) {
      this.moveNext();
    }
    return current;
  }

  @Override
  public boolean moveNext() {
    if (this.rowIterator.hasNext()) {
      final Object[] row = this.rowIterator.next();
      current = row;
      return true;
    } else {
      current = null;
      return false;
    }
  }

  @Override
  public void reset() {
    throw new UnsupportedOperationException();
  }

}

Шаг 4: Класс
Query Executor Query Executor может взять объект Schema и подготовить соединение и оператор для выполнения SQL. Этот метод execute принимает строку SQL и возвращает объект Java ResultSet. Обратите внимание, что код выполнения запроса на домашней странице Optiq github устарел, вы можете увидеть правильный путь в моем примере.

package io.thedal.optiq.javabean;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import net.hydromatic.optiq.SchemaPlus;
import net.hydromatic.optiq.jdbc.OptiqConnection;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JdbcQueryExecutor {

  final Logger logger = LoggerFactory.getLogger(JdbcQueryExecutor.class);
  private Connection connection;
  private Statement statement;

  public JdbcQueryExecutor(JavaBeanSchema schema) {
    try {
      Class.forName("net.hydromatic.optiq.jdbc.Driver");
      connection = DriverManager.getConnection("jdbc:optiq:");
      OptiqConnection optiqConnection = connection
          .unwrap(OptiqConnection.class);
      SchemaPlus rootSchema = optiqConnection.getRootSchema();
      rootSchema.add(schema.getName(), schema);
      logger.info("Created connection to schema: " + schema.getName());
    } catch (Exception e) {
      logger.error("Could not create Optiq Connection");
    }
  }

  public ResultSet execute(String sql) {
    ResultSet results = null;
    try {
      statement = connection.createStatement();
      results = statement.executeQuery(sql);
    } catch (SQLException e) {
      logger.error("Could not create a statement" + e);
    }
    return results;
  }

  public void close() {
    if (connection != null) {
      try {
        connection.close();
      } catch (SQLException e) {
        logger.error("Could not close Optiq connection");
      }
      if (statement != null) {
        try {
          statement.close();
        } catch (SQLException e) {
          logger.error("Could not close Optiq statement");
        }
      }
    }
  }

}

Собираем все вместе:
Оформите тестовую программу, которую я создал!

package io.thedal.optiq.javabean;

import static org.junit.Assert.*;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.junit.Test;

public class SampleTestProgram {

  @Test
  public void testQuery() {

    // Create test data
    User user1 = new User("Abishek", 29, "India");
    User user2 = new User("Kousik", 25, "Thailand");
    User user3 = new User("CP", 15, "Russia");
    User user4 = new User("Karthik", 29, "US");
    List<User> userList = new ArrayList<User>();
    userList.add(user1);
    userList.add(user2);
    userList.add(user3);
    userList.add(user4);

    // Create a test Schema
    JavaBeanSchema schema = new JavaBeanSchema("TESTDB");
    schema.addAsTable("USERS", userList);

    // Execute a Query on schema
    JdbcQueryExecutor queryExec = new JdbcQueryExecutor(schema);
    String sql = "select \"Age\" from \"TESTDB\".\"USERS\" where \"Name\"='Abishek'";
    ResultSet result = queryExec.execute(sql);

    // Verify results
    if (result != null) {
      int output = -1;
      try {
        while (result.next()) {
          output = result.getInt("Age");
        }
      } catch (SQLException e) {
        fail("Failed while iterating resultset");
      }
      assertEquals(output, 29);
    } else {
      fail("Null resultset");
    }

    queryExec.close();

  }

}

Следующим сложным шагом является отправка запросов в хранилище путем создания правил и сопоставления выражений SQL. Я надеюсь написать пост об этом в ближайшее время.

Приятного запроса!