/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF 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.jdbc; import net.hydromatic.avatica.*; import net.hydromatic.linq4j.*; import net.hydromatic.linq4j.expressions.ClassDeclaration; import net.hydromatic.linq4j.function.Function0; import net.hydromatic.optiq.*; import net.hydromatic.optiq.config.OptiqConnectionConfig; import net.hydromatic.optiq.impl.java.JavaTypeFactory; import net.hydromatic.optiq.prepare.OptiqPrepareImpl; import net.hydromatic.optiq.runtime.*; import org.eigenbase.rel.RelNode; import org.eigenbase.relopt.RelOptPlanner; import org.eigenbase.relopt.RelOptRule; import org.eigenbase.reltype.RelDataType; import org.eigenbase.reltype.RelDataTypeFactory; import org.eigenbase.sql.SqlNode; import org.eigenbase.sql.validate.SqlValidator; import org.eigenbase.util.Stacks; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.*; /** * API for a service that prepares statements for execution. */ public interface OptiqPrepare { Function0<OptiqPrepare> DEFAULT_FACTORY = new Function0<OptiqPrepare>() { public OptiqPrepare apply() { return new OptiqPrepareImpl(); } }; ThreadLocal<ArrayList<Context>> THREAD_CONTEXT_STACK = new ThreadLocal<ArrayList<Context>>() { @Override protected ArrayList<Context> initialValue() { return new ArrayList<Context>(); } }; ParseResult parse(Context context, String sql); ConvertResult convert(Context context, String sql); <T> PrepareResult<T> prepareSql( Context context, String sql, Queryable<T> expression, Type elementType, int maxRowCount); <T> PrepareResult<T> prepareQueryable( Context context, Queryable<T> queryable); /** Context for preparing a statement. */ interface Context { JavaTypeFactory getTypeFactory(); OptiqRootSchema getRootSchema(); List<String> getDefaultSchemaPath(); OptiqConnectionConfig config(); /** Returns the spark handler. Never null. */ SparkHandler spark(); DataContext getDataContext(); } /** Callback to register Spark as the main engine. */ public interface SparkHandler { RelNode flattenTypes(RelOptPlanner planner, RelNode rootRel, boolean restructure); void registerRules(RuleSetBuilder builder); boolean enabled(); Bindable compile(ClassDeclaration expr, String s); Object sparkContext(); /** Allows Spark to declare the rules it needs. */ interface RuleSetBuilder { void addRule(RelOptRule rule); void removeRule(RelOptRule rule); } } /** Namespace that allows us to define non-abstract methods inside an * interface. */ public static class Dummy { private static SparkHandler sparkHandler; /** Returns a spark handler. Returns a trivial handler, for which * {@link SparkHandler#enabled()} returns {@code false}, if {@code enable} * is {@code false} or if Spark is not on the class path. Never returns * null. */ public static synchronized SparkHandler getSparkHandler(boolean enable) { if (sparkHandler == null) { sparkHandler = enable ? createHandler() : new TrivialSparkHandler(); } return sparkHandler; } private static SparkHandler createHandler() { try { final Class<?> clazz = Class.forName("net.hydromatic.optiq.impl.spark.SparkHandlerImpl"); Method method = clazz.getMethod("instance"); return (OptiqPrepare.SparkHandler) method.invoke(null); } catch (ClassNotFoundException e) { return new TrivialSparkHandler(); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (ClassCastException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } public static void push(Context context) { Stacks.push(THREAD_CONTEXT_STACK.get(), context); } public static Context peek() { return Stacks.peek(THREAD_CONTEXT_STACK.get()); } public static void pop(Context context) { Stacks.pop(THREAD_CONTEXT_STACK.get(), context); } /** Implementation of {@link SparkHandler} that either does nothing or * throws for each method. Use this if Spark is not installed. */ private static class TrivialSparkHandler implements SparkHandler { public RelNode flattenTypes(RelOptPlanner planner, RelNode rootRel, boolean restructure) { return rootRel; } public void registerRules(RuleSetBuilder builder) { } public boolean enabled() { return false; } public Bindable compile(ClassDeclaration expr, String s) { throw new UnsupportedOperationException(); } public Object sparkContext() { throw new UnsupportedOperationException(); } } } /** The result of parsing and validating a SQL query. */ public static class ParseResult { public final OptiqPrepareImpl prepare; public final String sql; // for debug public final SqlNode sqlNode; public final RelDataType rowType; public final RelDataTypeFactory typeFactory; public ParseResult(OptiqPrepareImpl prepare, SqlValidator validator, String sql, SqlNode sqlNode, RelDataType rowType) { super(); this.prepare = prepare; this.sql = sql; this.sqlNode = sqlNode; this.rowType = rowType; this.typeFactory = validator.getTypeFactory(); } } /** The result of parsing and validating a SQL query and converting it to * relational algebra. */ public static class ConvertResult extends ParseResult { public final RelNode relNode; public ConvertResult(OptiqPrepareImpl prepare, SqlValidator validator, String sql, SqlNode sqlNode, RelDataType rowType, RelNode relNode) { super(prepare, validator, sql, sqlNode, rowType); this.relNode = relNode; } } /** The result of preparing a query. It gives the Avatica driver framework * the information it needs to create a prepared statement, or to execute a * statement directly, without an explicit prepare step. */ public static class PrepareResult<T> implements AvaticaPrepareResult { public final String sql; // for debug public final List<AvaticaParameter> parameterList; public final RelDataType rowType; public final ColumnMetaData.StructType structType; private final int maxRowCount; private final Bindable<T> bindable; public final Class resultClazz; public PrepareResult(String sql, List<AvaticaParameter> parameterList, RelDataType rowType, ColumnMetaData.StructType structType, int maxRowCount, Bindable<T> bindable, Class resultClazz) { super(); this.sql = sql; this.parameterList = parameterList; this.rowType = rowType; this.structType = structType; this.maxRowCount = maxRowCount; this.bindable = bindable; this.resultClazz = resultClazz; } public Cursor createCursor(DataContext dataContext) { Enumerator<?> enumerator = enumerator(dataContext); //noinspection unchecked return structType.columns.size() == 1 ? new ObjectEnumeratorCursor((Enumerator) enumerator) : resultClazz != null && !resultClazz.isArray() ? new RecordEnumeratorCursor((Enumerator) enumerator, resultClazz) : new ArrayEnumeratorCursor((Enumerator) enumerator); } public List<ColumnMetaData> getColumnList() { return structType.columns; } public List<AvaticaParameter> getParameterList() { return parameterList; } public String getSql() { return sql; } private Enumerable<T> getEnumerable(DataContext dataContext) { Enumerable<T> enumerable = bindable.bind(dataContext); if (maxRowCount >= 0) { // Apply limit. In JDBC 0 means "no limit". But for us, -1 means // "no limit", and 0 is a valid limit. enumerable = EnumerableDefaults.take(enumerable, maxRowCount); } return enumerable; } public Enumerator<T> enumerator(DataContext dataContext) { return getEnumerable(dataContext).enumerator(); } public Iterator<T> iterator(DataContext dataContext) { return getEnumerable(dataContext).iterator(); } } } // End OptiqPrepare.java