org.apache.calcite.schema.Schemas.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.calcite.schema.Schemas.java

Source

/*
 * 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 org.apache.calcite.schema;

import org.apache.calcite.DataContext;
import org.apache.calcite.adapter.java.JavaTypeFactory;
import org.apache.calcite.config.CalciteConnectionConfig;
import org.apache.calcite.config.CalciteConnectionConfigImpl;
import org.apache.calcite.config.CalciteConnectionProperty;
import org.apache.calcite.jdbc.CalciteConnection;
import org.apache.calcite.jdbc.CalcitePrepare;
import org.apache.calcite.jdbc.CalciteSchema;
import org.apache.calcite.linq4j.Enumerable;
import org.apache.calcite.linq4j.QueryProvider;
import org.apache.calcite.linq4j.Queryable;
import org.apache.calcite.linq4j.tree.Expression;
import org.apache.calcite.linq4j.tree.Expressions;
import org.apache.calcite.linq4j.tree.MethodCallExpression;
import org.apache.calcite.linq4j.tree.Types;
import org.apache.calcite.materialize.Lattice;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelProtoDataType;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.sql.type.SqlTypeUtil;
import org.apache.calcite.util.BuiltInMethod;
import org.apache.calcite.util.Pair;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;

import java.lang.reflect.Type;
import java.sql.Connection;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

/**
 * Utility functions for schemas.
 */
public final class Schemas {
    private static final com.google.common.base.Function<CalciteSchema.LatticeEntry, CalciteSchema.TableEntry> TO_TABLE_ENTRY = new com.google.common.base.Function<CalciteSchema.LatticeEntry, CalciteSchema.TableEntry>() {
        public CalciteSchema.TableEntry apply(CalciteSchema.LatticeEntry entry) {
            final CalciteSchema.TableEntry starTable = entry.getStarTable();
            assert starTable.getTable().getJdbcTableType() == Schema.TableType.STAR;
            return entry.getStarTable();
        }
    };

    private static final com.google.common.base.Function<CalciteSchema.LatticeEntry, Lattice> TO_LATTICE = new com.google.common.base.Function<CalciteSchema.LatticeEntry, Lattice>() {
        public Lattice apply(CalciteSchema.LatticeEntry entry) {
            return entry.getLattice();
        }
    };

    private Schemas() {
        throw new AssertionError("no instances!");
    }

    public static CalciteSchema.FunctionEntry resolve(RelDataTypeFactory typeFactory, String name,
            Collection<CalciteSchema.FunctionEntry> functionEntries, List<RelDataType> argumentTypes) {
        final List<CalciteSchema.FunctionEntry> matches = new ArrayList<>();
        for (CalciteSchema.FunctionEntry entry : functionEntries) {
            if (matches(typeFactory, entry.getFunction(), argumentTypes)) {
                matches.add(entry);
            }
        }
        switch (matches.size()) {
        case 0:
            return null;
        case 1:
            return matches.get(0);
        default:
            throw new RuntimeException("More than one match for " + name + " with arguments " + argumentTypes);
        }
    }

    private static boolean matches(RelDataTypeFactory typeFactory, Function member,
            List<RelDataType> argumentTypes) {
        List<FunctionParameter> parameters = member.getParameters();
        if (parameters.size() != argumentTypes.size()) {
            return false;
        }
        for (int i = 0; i < argumentTypes.size(); i++) {
            RelDataType argumentType = argumentTypes.get(i);
            FunctionParameter parameter = parameters.get(i);
            if (!canConvert(argumentType, parameter.getType(typeFactory))) {
                return false;
            }
        }
        return true;
    }

    private static boolean canConvert(RelDataType fromType, RelDataType toType) {
        return SqlTypeUtil.canAssignFrom(toType, fromType);
    }

    /** Returns the expression for a schema. */
    public static Expression expression(SchemaPlus schema) {
        return schema.getExpression(schema.getParentSchema(), schema.getName());
    }

    /** Returns the expression for a sub-schema. */
    public static Expression subSchemaExpression(SchemaPlus schema, String name, Class type) {
        // (Type) schemaExpression.getSubSchema("name")
        final Expression schemaExpression = expression(schema);
        Expression call = Expressions.call(schemaExpression, BuiltInMethod.SCHEMA_GET_SUB_SCHEMA.method,
                Expressions.constant(name));
        //CHECKSTYLE: IGNORE 2
        //noinspection unchecked
        if (false && type != null && !type.isAssignableFrom(Schema.class)) {
            return unwrap(call, type);
        }
        return call;
    }

    /** Converts a schema expression to a given type by calling the
     * {@link SchemaPlus#unwrap(Class)} method. */
    public static Expression unwrap(Expression call, Class type) {
        return Expressions.convert_(
                Expressions.call(call, BuiltInMethod.SCHEMA_PLUS_UNWRAP.method, Expressions.constant(type)), type);
    }

    /** Returns the expression to access a table within a schema. */
    public static Expression tableExpression(SchemaPlus schema, Type elementType, String tableName, Class clazz) {
        final MethodCallExpression expression;
        if (Table.class.isAssignableFrom(clazz)) {
            expression = Expressions.call(expression(schema), BuiltInMethod.SCHEMA_GET_TABLE.method,
                    Expressions.constant(tableName));
            if (ScannableTable.class.isAssignableFrom(clazz)) {
                return Expressions.call(BuiltInMethod.SCHEMAS_ENUMERABLE_SCANNABLE.method,
                        Expressions.convert_(expression, ScannableTable.class), DataContext.ROOT);
            }
            if (FilterableTable.class.isAssignableFrom(clazz)) {
                return Expressions.call(BuiltInMethod.SCHEMAS_ENUMERABLE_FILTERABLE.method,
                        Expressions.convert_(expression, FilterableTable.class), DataContext.ROOT);
            }
            if (ProjectableFilterableTable.class.isAssignableFrom(clazz)) {
                return Expressions.call(BuiltInMethod.SCHEMAS_ENUMERABLE_PROJECTABLE_FILTERABLE.method,
                        Expressions.convert_(expression, ProjectableFilterableTable.class), DataContext.ROOT);
            }
        } else {
            expression = Expressions.call(BuiltInMethod.SCHEMAS_QUERYABLE.method, DataContext.ROOT,
                    expression(schema), Expressions.constant(elementType), Expressions.constant(tableName));
        }
        return Types.castIfNecessary(clazz, expression);
    }

    public static DataContext createDataContext(Connection connection) {
        return new DummyDataContext((CalciteConnection) connection);
    }

    /** Returns a {@link Queryable}, given a fully-qualified table name. */
    public static <E> Queryable<E> queryable(DataContext root, Class<E> clazz, String... names) {
        return queryable(root, clazz, Arrays.asList(names));
    }

    /** Returns a {@link Queryable}, given a fully-qualified table name as an
     * iterable. */
    public static <E> Queryable<E> queryable(DataContext root, Class<E> clazz, Iterable<? extends String> names) {
        SchemaPlus schema = root.getRootSchema();
        for (Iterator<? extends String> iterator = names.iterator();;) {
            String name = iterator.next();
            if (iterator.hasNext()) {
                schema = schema.getSubSchema(name);
            } else {
                return queryable(root, schema, clazz, name);
            }
        }
    }

    /** Returns a {@link Queryable}, given a schema and table name. */
    public static <E> Queryable<E> queryable(DataContext root, SchemaPlus schema, Class<E> clazz,
            String tableName) {
        QueryableTable table = (QueryableTable) schema.getTable(tableName);
        return table.asQueryable(root.getQueryProvider(), schema, tableName);
    }

    /** Returns an {@link org.apache.calcite.linq4j.Enumerable} over the rows of
     * a given table, representing each row as an object array. */
    public static Enumerable<Object[]> enumerable(final ScannableTable table, final DataContext root) {
        return table.scan(root);
    }

    /** Returns an {@link org.apache.calcite.linq4j.Enumerable} over the rows of
     * a given table, not applying any filters, representing each row as an object
     * array. */
    public static Enumerable<Object[]> enumerable(final FilterableTable table, final DataContext root) {
        return table.scan(root, ImmutableList.<RexNode>of());
    }

    /** Returns an {@link org.apache.calcite.linq4j.Enumerable} over the rows of
     * a given table, not applying any filters and projecting all columns,
     * representing each row as an object array. */
    public static Enumerable<Object[]> enumerable(final ProjectableFilterableTable table, final DataContext root) {
        return table.scan(root, ImmutableList.<RexNode>of(),
                identity(table.getRowType(root.getTypeFactory()).getFieldCount()));
    }

    private static int[] identity(int count) {
        final int[] integers = new int[count];
        for (int i = 0; i < integers.length; i++) {
            integers[i] = i;
        }
        return integers;
    }

    /** Returns an {@link org.apache.calcite.linq4j.Enumerable} over object
     * arrays, given a fully-qualified table name which leads to a
     * {@link ScannableTable}. */
    public static Table table(DataContext root, String... names) {
        SchemaPlus schema = root.getRootSchema();
        final List<String> nameList = Arrays.asList(names);
        for (Iterator<? extends String> iterator = nameList.iterator();;) {
            String name = iterator.next();
            if (iterator.hasNext()) {
                schema = schema.getSubSchema(name);
            } else {
                return schema.getTable(name);
            }
        }
    }

    /** Parses and validates a SQL query. For use within Calcite only. */
    public static CalcitePrepare.ParseResult parse(final CalciteConnection connection, final CalciteSchema schema,
            final List<String> schemaPath, final String sql) {
        final CalcitePrepare prepare = CalcitePrepare.DEFAULT_FACTORY.apply();
        final ImmutableMap<CalciteConnectionProperty, String> propValues = ImmutableMap.of();
        final CalcitePrepare.Context context = makeContext(connection, schema, schemaPath, propValues);
        CalcitePrepare.Dummy.push(context);
        try {
            return prepare.parse(context, sql);
        } finally {
            CalcitePrepare.Dummy.pop(context);
        }
    }

    /** Parses and validates a SQL query and converts to relational algebra. For
     * use within Calcite only. */
    public static CalcitePrepare.ConvertResult convert(final CalciteConnection connection,
            final CalciteSchema schema, final List<String> schemaPath, final String sql) {
        final CalcitePrepare prepare = CalcitePrepare.DEFAULT_FACTORY.apply();
        final ImmutableMap<CalciteConnectionProperty, String> propValues = ImmutableMap.of();
        final CalcitePrepare.Context context = makeContext(connection, schema, schemaPath, propValues);
        CalcitePrepare.Dummy.push(context);
        try {
            return prepare.convert(context, sql);
        } finally {
            CalcitePrepare.Dummy.pop(context);
        }
    }

    /** Analyzes a view. For use within Calcite only. */
    public static CalcitePrepare.AnalyzeViewResult analyzeView(final CalciteConnection connection,
            final CalciteSchema schema, final List<String> schemaPath, final String sql, boolean fail) {
        final CalcitePrepare prepare = CalcitePrepare.DEFAULT_FACTORY.apply();
        final ImmutableMap<CalciteConnectionProperty, String> propValues = ImmutableMap.of();
        final CalcitePrepare.Context context = makeContext(connection, schema, schemaPath, propValues);
        CalcitePrepare.Dummy.push(context);
        try {
            return prepare.analyzeView(context, sql, fail);
        } finally {
            CalcitePrepare.Dummy.pop(context);
        }
    }

    /** Prepares a SQL query for execution. For use within Calcite only. */
    public static CalcitePrepare.CalciteSignature<Object> prepare(final CalciteConnection connection,
            final CalciteSchema schema, final List<String> schemaPath, final String sql,
            final ImmutableMap<CalciteConnectionProperty, String> map) {
        final CalcitePrepare prepare = CalcitePrepare.DEFAULT_FACTORY.apply();
        final CalcitePrepare.Context context = makeContext(connection, schema, schemaPath, map);
        CalcitePrepare.Dummy.push(context);
        try {
            return prepare.prepareSql(context, CalcitePrepare.Query.of(sql), Object[].class, -1);
        } finally {
            CalcitePrepare.Dummy.pop(context);
        }
    }

    public static CalcitePrepare.Context makeContext(final CalciteConnection connection, final CalciteSchema schema,
            final List<String> schemaPath, final ImmutableMap<CalciteConnectionProperty, String> propValues) {
        if (connection == null) {
            final CalcitePrepare.Context context0 = CalcitePrepare.Dummy.peek();
            final CalciteConnectionConfig config = mutate(context0.config(), propValues);
            return makeContext(config, context0.getTypeFactory(), context0.getDataContext(), schema, schemaPath);
        } else {
            final CalciteConnectionConfig config = mutate(connection.config(), propValues);
            return makeContext(config, connection.getTypeFactory(), createDataContext(connection), schema,
                    schemaPath);
        }
    }

    private static CalciteConnectionConfig mutate(CalciteConnectionConfig config,
            ImmutableMap<CalciteConnectionProperty, String> propValues) {
        for (Map.Entry<CalciteConnectionProperty, String> e : propValues.entrySet()) {
            config = ((CalciteConnectionConfigImpl) config).set(e.getKey(), e.getValue());
        }
        return config;
    }

    private static CalcitePrepare.Context makeContext(final CalciteConnectionConfig connectionConfig,
            final JavaTypeFactory typeFactory, final DataContext dataContext, final CalciteSchema schema,
            final List<String> schemaPath) {
        return new CalcitePrepare.Context() {
            public JavaTypeFactory getTypeFactory() {
                return typeFactory;
            }

            public CalciteSchema getRootSchema() {
                return schema.root();
            }

            public List<String> getDefaultSchemaPath() {
                // schemaPath is usually null. If specified, it overrides schema
                // as the context within which the SQL is validated.
                if (schemaPath == null) {
                    return schema.path(null);
                }
                return schemaPath;
            }

            public CalciteConnectionConfig config() {
                return connectionConfig;
            }

            public DataContext getDataContext() {
                return dataContext;
            }

            public CalcitePrepare.SparkHandler spark() {
                final boolean enable = config().spark();
                return CalcitePrepare.Dummy.getSparkHandler(enable);
            }
        };
    }

    /** Returns an implementation of
     * {@link RelProtoDataType}
     * that asks a given table for its row type with a given type factory. */
    public static RelProtoDataType proto(final Table table) {
        return new RelProtoDataType() {
            public RelDataType apply(RelDataTypeFactory typeFactory) {
                return table.getRowType(typeFactory);
            }
        };
    }

    /** Returns an implementation of {@link RelProtoDataType}
     * that asks a given scalar function for its return type with a given type
     * factory. */
    public static RelProtoDataType proto(final ScalarFunction function) {
        return new RelProtoDataType() {
            public RelDataType apply(RelDataTypeFactory typeFactory) {
                return function.getReturnType(typeFactory);
            }
        };
    }

    /** Returns the star tables defined in a schema.
     *
     * @param schema Schema */
    public static List<CalciteSchema.TableEntry> getStarTables(CalciteSchema schema) {
        final List<CalciteSchema.LatticeEntry> list = getLatticeEntries(schema);
        return Lists.transform(list, TO_TABLE_ENTRY);
    }

    /** Returns the lattices defined in a schema.
     *
     * @param schema Schema */
    public static List<Lattice> getLattices(CalciteSchema schema) {
        final List<CalciteSchema.LatticeEntry> list = getLatticeEntries(schema);
        return Lists.transform(list, TO_LATTICE);
    }

    /** Returns the lattices defined in a schema.
     *
     * @param schema Schema */
    public static List<CalciteSchema.LatticeEntry> getLatticeEntries(CalciteSchema schema) {
        final List<CalciteSchema.LatticeEntry> list = Lists.newArrayList();
        gatherLattices(schema, list);
        return list;
    }

    private static void gatherLattices(CalciteSchema schema, List<CalciteSchema.LatticeEntry> list) {
        list.addAll(schema.getLatticeMap().values());
        for (CalciteSchema subSchema : schema.getSubSchemaMap().values()) {
            gatherLattices(subSchema, list);
        }
    }

    /** Returns a sub-schema of a given schema obtained by following a sequence
     * of names.
     *
     * <p>The result is null if the initial schema is null or any sub-schema does
     * not exist.
     */
    public static CalciteSchema subSchema(CalciteSchema schema, Iterable<String> names) {
        for (String string : names) {
            if (schema == null) {
                return null;
            }
            schema = schema.getSubSchema(string, false);
        }
        return schema;
    }

    /** Generates a table name that is unique within the given schema. */
    public static String uniqueTableName(CalciteSchema schema, String base) {
        String t = Preconditions.checkNotNull(base);
        for (int x = 0; schema.getTable(t, true) != null; x++) {
            t = base + x;
        }
        return t;
    }

    /** Creates a path with a given list of names starting from a given root
     * schema. */
    public static Path path(CalciteSchema rootSchema, Iterable<String> names) {
        final ImmutableList.Builder<Pair<String, Schema>> builder = ImmutableList.builder();
        Schema schema = rootSchema.plus();
        final Iterator<String> iterator = names.iterator();
        if (!iterator.hasNext()) {
            return PathImpl.EMPTY;
        }
        if (!rootSchema.name.isEmpty()) {
            assert rootSchema.name.equals(iterator.next());
        }
        for (;;) {
            final String name = iterator.next();
            builder.add(Pair.of(name, schema));
            if (!iterator.hasNext()) {
                return path(builder.build());
            }
            schema = schema.getSubSchema(name);
        }
    }

    public static PathImpl path(ImmutableList<Pair<String, Schema>> build) {
        return new PathImpl(build);
    }

    /** Returns the path to get to a schema from its root. */
    public static Path path(SchemaPlus schema) {
        List<Pair<String, Schema>> list = new ArrayList<>();
        for (SchemaPlus s = schema; s != null; s = s.getParentSchema()) {
            list.add(Pair.<String, Schema>of(s.getName(), s));
        }
        return new PathImpl(ImmutableList.copyOf(Lists.reverse(list)));
    }

    /** Dummy data context that has no variables. */
    private static class DummyDataContext implements DataContext {
        private final CalciteConnection connection;
        private final ImmutableMap<String, Object> map;

        public DummyDataContext(CalciteConnection connection) {
            this.connection = connection;
            this.map = ImmutableMap.<String, Object>of("timeZone", TimeZone.getDefault());
        }

        public SchemaPlus getRootSchema() {
            return connection.getRootSchema();
        }

        public JavaTypeFactory getTypeFactory() {
            return connection.getTypeFactory();
        }

        public QueryProvider getQueryProvider() {
            return connection;
        }

        public Object get(String name) {
            return map.get(name);
        }
    }

    /** Implementation of {@link Path}. */
    private static class PathImpl extends AbstractList<Pair<String, Schema>> implements Path {
        private final ImmutableList<Pair<String, Schema>> pairs;

        private static final PathImpl EMPTY = new PathImpl(ImmutableList.<Pair<String, Schema>>of());

        PathImpl(ImmutableList<Pair<String, Schema>> pairs) {
            this.pairs = pairs;
        }

        @Override
        public boolean equals(Object o) {
            return this == o || o instanceof PathImpl && pairs.equals(((PathImpl) o).pairs);
        }

        @Override
        public int hashCode() {
            return pairs.hashCode();
        }

        public Pair<String, Schema> get(int index) {
            return pairs.get(index);
        }

        public int size() {
            return pairs.size();
        }

        public Path parent() {
            if (pairs.isEmpty()) {
                throw new IllegalArgumentException("at root");
            }
            return new PathImpl(pairs.subList(0, pairs.size() - 1));
        }

        public List<String> names() {
            return new AbstractList<String>() {
                public String get(int index) {
                    return pairs.get(index + 1).left;
                }

                public int size() {
                    return pairs.size() - 1;
                }
            };
        }

        public List<Schema> schemas() {
            return Pair.right(pairs);
        }
    }
}

// End Schemas.java