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