В этом посте показано, как создать адаптер 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. Я надеюсь написать пост об этом в ближайшее время.
Приятного запроса!