All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.calcite.avatica.MetaImpl Maven / Gradle / Ivy

The newest version!
/*
 * 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.avatica;

import org.apache.calcite.avatica.remote.TypedValue;
import org.apache.calcite.avatica.util.ArrayIteratorCursor;
import org.apache.calcite.avatica.util.Cursor;
import org.apache.calcite.avatica.util.IteratorCursor;
import org.apache.calcite.avatica.util.ListIteratorCursor;
import org.apache.calcite.avatica.util.MapIteratorCursor;
import org.apache.calcite.avatica.util.RecordIteratorCursor;

import com.fasterxml.jackson.annotation.JsonIgnore;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

/**
 * Basic implementation of {@link Meta}.
 *
 * 

Each sub-class must implement the two remaining abstract methods, * {@link #prepare} and * {@link #prepareAndExecute}. * It should also override metadata methods such as {@link #getCatalogs(Meta.ConnectionHandle)} and * {@link #getTables} for the element types for which it has instances; the * default metadata methods return empty collections. */ public abstract class MetaImpl implements Meta { /** The {@link AvaticaConnection} backing {@code this}. */ protected final AvaticaConnection connection; /** Represents the various states specific to {@link #connection}. * *

Note: this instance is used recursively with {@link #connection}'s getter and setter * methods.

*/ protected final ConnectionPropertiesImpl connProps; public MetaImpl(AvaticaConnection connection) { this.connection = connection; this.connProps = new ConnectionPropertiesImpl(); } /** Uses a {@link org.apache.calcite.avatica.Meta.CursorFactory} to convert * an {@link Iterable} into a * {@link org.apache.calcite.avatica.util.Cursor}. */ public static Cursor createCursor(CursorFactory cursorFactory, Iterable iterable) { switch (cursorFactory.style) { case OBJECT: return new IteratorCursor(iterable.iterator()) { protected Getter createGetter(int ordinal) { return new ObjectGetter(ordinal); } }; case ARRAY: @SuppressWarnings("unchecked") final Iterable iterable1 = (Iterable) (Iterable) iterable; return new ArrayIteratorCursor(iterable1.iterator()); case RECORD: @SuppressWarnings("unchecked") final Class clazz = cursorFactory.clazz; return new RecordIteratorCursor(iterable.iterator(), clazz); case RECORD_PROJECTION: @SuppressWarnings("unchecked") final Class clazz2 = cursorFactory.clazz; return new RecordIteratorCursor(iterable.iterator(), clazz2, cursorFactory.fields); case LIST: @SuppressWarnings("unchecked") final Iterable> iterable2 = (Iterable>) (Iterable) iterable; return new ListIteratorCursor(iterable2.iterator()); case MAP: @SuppressWarnings("unchecked") final Iterable> iterable3 = (Iterable>) (Iterable) iterable; return new MapIteratorCursor(iterable3.iterator(), cursorFactory.fieldNames); default: throw new AssertionError("unknown style: " + cursorFactory.style); } } public static List> collect(CursorFactory cursorFactory, final Iterator iterator, List> list) { final Iterable iterable = new Iterable() { public Iterator iterator() { return iterator; } }; return collect(cursorFactory, iterable, list); } public static List> collect(CursorFactory cursorFactory, Iterable iterable, List> list) { switch (cursorFactory.style) { case OBJECT: for (Object o : iterable) { list.add(Collections.singletonList(o)); } return list; case ARRAY: @SuppressWarnings("unchecked") final Iterable iterable1 = (Iterable) (Iterable) iterable; for (Object[] objects : iterable1) { list.add(Arrays.asList(objects)); } return list; case RECORD: case RECORD_PROJECTION: final Field[] fields; switch (cursorFactory.style) { case RECORD: fields = cursorFactory.clazz.getFields(); break; default: fields = cursorFactory.fields.toArray( new Field[cursorFactory.fields.size()]); } for (Object o : iterable) { final Object[] objects = new Object[fields.length]; for (int i = 0; i < fields.length; i++) { Field field = fields[i]; try { objects[i] = field.get(o); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } list.add(Arrays.asList(objects)); } return list; case LIST: @SuppressWarnings("unchecked") final Iterable> iterable2 = (Iterable>) (Iterable) iterable; for (List objects : iterable2) { list.add(objects); } return list; case MAP: @SuppressWarnings("unchecked") final Iterable> iterable3 = (Iterable>) (Iterable) iterable; for (Map map : iterable3) { final List objects = new ArrayList(); for (String fieldName : cursorFactory.fieldNames) { objects.add(map.get(fieldName)); } list.add(objects); } return list; default: throw new AssertionError("unknown style: " + cursorFactory.style); } } @Override public void openConnection(ConnectionHandle ch, Map info) { // dummy implementation, connection is already created at this point } @Override public void closeConnection(ConnectionHandle ch) { // TODO: implement // // lots of Calcite tests break with this simple implementation, // requires investigation // try { // connection.close(); // } catch (SQLException e) { // throw new RuntimeException(e); // } } @Override public ConnectionProperties connectionSync(ConnectionHandle ch, ConnectionProperties connProps) { this.connProps.merge(connProps); this.connProps.setDirty(false); return this.connProps; } public StatementHandle createStatement(ConnectionHandle ch) { return new StatementHandle(ch.id, connection.statementCount++, null); } /** Creates an empty result set. Useful for JDBC metadata methods that are * not implemented or which query entities that are not supported (e.g. * triggers in Lingual). */ protected MetaResultSet createEmptyResultSet(final Class clazz) { return createResultSet(Collections.emptyMap(), fieldMetaData(clazz).columns, CursorFactory.deduce(fieldMetaData(clazz).columns, null), Frame.EMPTY); } public static ColumnMetaData columnMetaData(String name, int index, Class type) { TypeInfo pair = TypeInfo.m.get(type); ColumnMetaData.Rep rep = ColumnMetaData.Rep.VALUE_MAP.get(type); ColumnMetaData.AvaticaType scalarType = ColumnMetaData.scalar(pair.sqlType, pair.sqlTypeName, rep); return new ColumnMetaData( index, false, true, false, false, pair.primitive ? DatabaseMetaData.columnNullable : DatabaseMetaData.columnNoNulls, true, -1, name, name, null, 0, 0, null, null, scalarType, true, false, false, scalarType.columnClassName()); } protected static ColumnMetaData.StructType fieldMetaData(Class clazz) { final List list = new ArrayList(); for (Field field : clazz.getFields()) { if (Modifier.isPublic(field.getModifiers()) && !Modifier.isStatic(field.getModifiers())) { list.add( columnMetaData( AvaticaUtils.camelToUpper(field.getName()), list.size() + 1, field.getType())); } } return ColumnMetaData.struct(list); } protected MetaResultSet createResultSet( Map internalParameters, List columns, CursorFactory cursorFactory, Frame firstFrame) { try { final AvaticaStatement statement = connection.createStatement(); final Signature signature = new Signature(columns, "", Collections.emptyList(), internalParameters, cursorFactory, Meta.StatementType.SELECT); return MetaResultSet.create(connection.id, statement.getId(), true, signature, firstFrame); } catch (SQLException e) { throw new RuntimeException(e); } } /** An object that has a name. */ public interface Named { @JsonIgnore String getName(); } /** Metadata describing a column. */ public static class MetaColumn implements Named { public final String tableCat; public final String tableSchem; public final String tableName; public final String columnName; public final int dataType; public final String typeName; public final int columnSize; public final String bufferLength = null; public final Integer decimalDigits; public final int numPrecRadix; public final int nullable; public final String remarks = null; public final String columnDef = null; public final String sqlDataType = null; public final String sqlDatetimeSub = null; public final int charOctetLength; public final int ordinalPosition; public final String isNullable; public final String scopeCatalog = null; public final String scopeSchema = null; public final String scopeTable = null; public final String sourceDataType = null; public final String isAutoincrement = null; public final String isGeneratedcolumn = null; public MetaColumn( String tableCat, String tableSchem, String tableName, String columnName, int dataType, String typeName, int columnSize, Integer decimalDigits, int numPrecRadix, int nullable, int charOctetLength, int ordinalPosition, String isNullable) { this.tableCat = tableCat; this.tableSchem = tableSchem; this.tableName = tableName; this.columnName = columnName; this.dataType = dataType; this.typeName = typeName; this.columnSize = columnSize; this.decimalDigits = decimalDigits; this.numPrecRadix = numPrecRadix; this.nullable = nullable; this.charOctetLength = charOctetLength; this.ordinalPosition = ordinalPosition; this.isNullable = isNullable; } public String getName() { return columnName; } } /** Metadata describing a TypeInfo. */ /** Metadata describing a table. */ public static class MetaTable implements Named { public final String tableCat; public final String tableSchem; public final String tableName; public final String tableType; public final String remarks = null; public final String typeCat = null; public final String typeSchem = null; public final String typeName = null; public final String selfReferencingColName = null; public final String refGeneration = null; public MetaTable(String tableCat, String tableSchem, String tableName, String tableType) { this.tableCat = tableCat; this.tableSchem = tableSchem; this.tableName = tableName; this.tableType = tableType; } public String getName() { return tableName; } } /** Metadata describing a schema. */ public static class MetaSchema implements Named { public final String tableCatalog; public final String tableSchem; public MetaSchema( String tableCatalog, String tableSchem) { this.tableCatalog = tableCatalog; this.tableSchem = tableSchem; } public String getName() { return tableSchem; } } /** Metadata describing a catalog. */ public static class MetaCatalog implements Named { public final String tableCat; public MetaCatalog( String tableCatalog) { this.tableCat = tableCatalog; } public String getName() { return tableCat; } } /** Metadata describing a table type. */ public static class MetaTableType { public final String tableType; public MetaTableType(String tableType) { this.tableType = tableType; } } /** Metadata describing a procedure. */ public static class MetaProcedure { } /** Metadata describing a procedure column. */ public static class MetaProcedureColumn { } /** Metadata describing a column privilege. */ public static class MetaColumnPrivilege { } /** Metadata describing a table privilege. */ public static class MetaTablePrivilege { } /** Metadata describing the best identifier for a row. */ public static class MetaBestRowIdentifier { } /** Metadata describing a version column. */ public static class MetaVersionColumn { public final short scope; public final String columnName; public final int dataType; public final String typeName; public final int columnSize; public final int bufferLength; public final short decimalDigits; public final short pseudoColumn; MetaVersionColumn(short scope, String columnName, int dataType, String typeName, int columnSize, int bufferLength, short decimalDigits, short pseudoColumn) { this.scope = scope; this.columnName = columnName; this.dataType = dataType; this.typeName = typeName; this.columnSize = columnSize; this.bufferLength = bufferLength; this.decimalDigits = decimalDigits; this.pseudoColumn = pseudoColumn; } } /** Metadata describing a primary key. */ public static class MetaPrimaryKey { public final String tableCat; public final String tableSchem; public final String tableName; public final String columnName; public final short keySeq; public final String pkName; MetaPrimaryKey(String tableCat, String tableSchem, String tableName, String columnName, short keySeq, String pkName) { this.tableCat = tableCat; this.tableSchem = tableSchem; this.tableName = tableName; this.columnName = columnName; this.keySeq = keySeq; this.pkName = pkName; } } /** Metadata describing an imported key. */ public static class MetaImportedKey { } /** Metadata describing an exported key. */ public static class MetaExportedKey { } /** Metadata describing a cross reference. */ public static class MetaCrossReference { } /** Metadata describing type info. */ public static class MetaTypeInfo implements Named { public final String typeName; public final int dataType; public final int precision; public final String literalPrefix; public final String literalSuffix; //TODO: Add create parameter for type on DDL public final String createParams = null; public final int nullable; public final boolean caseSensitive; public final int searchable; public final boolean unsignedAttribute; public final boolean fixedPrecScale; public final boolean autoIncrement; public final String localTypeName; public final int minimumScale; public final int maximumScale; public final int sqlDataType = 0; public final int sqlDatetimeSub = 0; public final Integer numPrecRadix; //nullable int public MetaTypeInfo( String typeName, int dataType, int precision, String literalPrefix, String literalSuffix, int nullable, boolean caseSensitive, int searchable, boolean unsignedAttribute, boolean fixedPrecScale, boolean autoIncrement, int minimumScale, int maximumScale, int numPrecRadix) { this.typeName = typeName; this.dataType = dataType; this.precision = precision; this.literalPrefix = literalPrefix; this.literalSuffix = literalSuffix; this.nullable = nullable; this.caseSensitive = caseSensitive; this.searchable = searchable; this.unsignedAttribute = unsignedAttribute; this.fixedPrecScale = fixedPrecScale; this.autoIncrement = autoIncrement; this.localTypeName = typeName; // Make min to be 0 instead of -1 this.minimumScale = minimumScale == -1 ? 0 : minimumScale; this.maximumScale = maximumScale == -1 ? 0 : maximumScale; this.numPrecRadix = numPrecRadix == 0 ? null : numPrecRadix; } public String getName() { return typeName; } } /** Metadata describing index info. */ public static class MetaIndexInfo { } /** Metadata describing a user-defined type. */ public static class MetaUdt { } /** Metadata describing a super-type. */ public static class MetaSuperType { } /** Metadata describing an attribute. */ public static class MetaAttribute { } /** Metadata describing a client info property. */ public static class MetaClientInfoProperty { } /** Metadata describing a function. */ public static class MetaFunction { } /** Metadata describing a function column. */ public static class MetaFunctionColumn { } /** Metadata describing a pseudo column. */ public static class MetaPseudoColumn { } /** Metadata describing a super-table. */ public static class MetaSuperTable { } public Map getDatabaseProperties(ConnectionHandle ch) { return Collections.emptyMap(); } public MetaResultSet getTables(ConnectionHandle ch, String catalog, Pat schemaPattern, Pat tableNamePattern, List typeList) { return createEmptyResultSet(MetaTable.class); } public MetaResultSet getColumns(ConnectionHandle ch, String catalog, Pat schemaPattern, Pat tableNamePattern, Pat columnNamePattern) { return createEmptyResultSet(MetaColumn.class); } public MetaResultSet getSchemas(ConnectionHandle ch, String catalog, Pat schemaPattern) { return createEmptyResultSet(MetaSchema.class); } public MetaResultSet getCatalogs(ConnectionHandle ch) { return createEmptyResultSet(MetaCatalog.class); } public MetaResultSet getTableTypes(ConnectionHandle ch) { return createEmptyResultSet(MetaTableType.class); } public MetaResultSet getProcedures(ConnectionHandle ch, String catalog, Pat schemaPattern, Pat procedureNamePattern) { return createEmptyResultSet(MetaProcedure.class); } public MetaResultSet getProcedureColumns(ConnectionHandle ch, String catalog, Pat schemaPattern, Pat procedureNamePattern, Pat columnNamePattern) { return createEmptyResultSet(MetaProcedureColumn.class); } public MetaResultSet getColumnPrivileges(ConnectionHandle ch, String catalog, String schema, String table, Pat columnNamePattern) { return createEmptyResultSet(MetaColumnPrivilege.class); } public MetaResultSet getTablePrivileges(ConnectionHandle ch, String catalog, Pat schemaPattern, Pat tableNamePattern) { return createEmptyResultSet(MetaTablePrivilege.class); } public MetaResultSet getBestRowIdentifier(ConnectionHandle ch, String catalog, String schema, String table, int scope, boolean nullable) { return createEmptyResultSet(MetaBestRowIdentifier.class); } public MetaResultSet getVersionColumns(ConnectionHandle ch, String catalog, String schema, String table) { return createEmptyResultSet(MetaVersionColumn.class); } public MetaResultSet getPrimaryKeys(ConnectionHandle ch, String catalog, String schema, String table) { return createEmptyResultSet(MetaPrimaryKey.class); } public MetaResultSet getImportedKeys(ConnectionHandle ch, String catalog, String schema, String table) { return createEmptyResultSet(MetaImportedKey.class); } public MetaResultSet getExportedKeys(ConnectionHandle ch, String catalog, String schema, String table) { return createEmptyResultSet(MetaExportedKey.class); } public MetaResultSet getCrossReference(ConnectionHandle ch, String parentCatalog, String parentSchema, String parentTable, String foreignCatalog, String foreignSchema, String foreignTable) { return createEmptyResultSet(MetaCrossReference.class); } public MetaResultSet getTypeInfo(ConnectionHandle ch) { return createEmptyResultSet(MetaTypeInfo.class); } public MetaResultSet getIndexInfo(ConnectionHandle ch, String catalog, String schema, String table, boolean unique, boolean approximate) { return createEmptyResultSet(MetaIndexInfo.class); } public MetaResultSet getUDTs(ConnectionHandle ch, String catalog, Pat schemaPattern, Pat typeNamePattern, int[] types) { return createEmptyResultSet(MetaUdt.class); } public MetaResultSet getSuperTypes(ConnectionHandle ch, String catalog, Pat schemaPattern, Pat typeNamePattern) { return createEmptyResultSet(MetaSuperType.class); } public MetaResultSet getSuperTables(ConnectionHandle ch, String catalog, Pat schemaPattern, Pat tableNamePattern) { return createEmptyResultSet(MetaSuperTable.class); } public MetaResultSet getAttributes(ConnectionHandle ch, String catalog, Pat schemaPattern, Pat typeNamePattern, Pat attributeNamePattern) { return createEmptyResultSet(MetaAttribute.class); } public MetaResultSet getClientInfoProperties(ConnectionHandle ch) { return createEmptyResultSet(MetaClientInfoProperty.class); } public MetaResultSet getFunctions(ConnectionHandle ch, String catalog, Pat schemaPattern, Pat functionNamePattern) { return createEmptyResultSet(MetaFunction.class); } public MetaResultSet getFunctionColumns(ConnectionHandle ch, String catalog, Pat schemaPattern, Pat functionNamePattern, Pat columnNamePattern) { return createEmptyResultSet(MetaFunctionColumn.class); } public MetaResultSet getPseudoColumns(ConnectionHandle ch, String catalog, Pat schemaPattern, Pat tableNamePattern, Pat columnNamePattern) { return createEmptyResultSet(MetaPseudoColumn.class); } @Override public Iterable createIterable(StatementHandle handle, QueryState state, Signature signature, List parameterValues, Frame firstFrame) { if (firstFrame != null && firstFrame.done) { return firstFrame.rows; } AvaticaStatement stmt; try { stmt = connection.lookupStatement(handle); } catch (SQLException e) { throw new RuntimeException(e); } return new FetchIterable(stmt, state, firstFrame, parameterValues); } public Frame fetch(AvaticaStatement stmt, List parameterValues, long offset, int fetchMaxRowCount) throws NoSuchStatementException, MissingResultsException { return null; } /** Information about a type. */ private static class TypeInfo { private static Map, TypeInfo> m = new HashMap, TypeInfo>(); static { put(boolean.class, true, Types.BOOLEAN, "BOOLEAN"); put(Boolean.class, false, Types.BOOLEAN, "BOOLEAN"); put(byte.class, true, Types.TINYINT, "TINYINT"); put(Byte.class, false, Types.TINYINT, "TINYINT"); put(short.class, true, Types.SMALLINT, "SMALLINT"); put(Short.class, false, Types.SMALLINT, "SMALLINT"); put(int.class, true, Types.INTEGER, "INTEGER"); put(Integer.class, false, Types.INTEGER, "INTEGER"); put(long.class, true, Types.BIGINT, "BIGINT"); put(Long.class, false, Types.BIGINT, "BIGINT"); put(float.class, true, Types.FLOAT, "FLOAT"); put(Float.class, false, Types.FLOAT, "FLOAT"); put(double.class, true, Types.DOUBLE, "DOUBLE"); put(Double.class, false, Types.DOUBLE, "DOUBLE"); put(String.class, false, Types.VARCHAR, "VARCHAR"); put(java.sql.Date.class, false, Types.DATE, "DATE"); put(Time.class, false, Types.TIME, "TIME"); put(Timestamp.class, false, Types.TIMESTAMP, "TIMESTAMP"); } private final boolean primitive; private final int sqlType; private final String sqlTypeName; public TypeInfo(boolean primitive, int sqlType, String sqlTypeName) { this.primitive = primitive; this.sqlType = sqlType; this.sqlTypeName = sqlTypeName; } static void put(Class clazz, boolean primitive, int sqlType, String sqlTypeName) { m.put(clazz, new TypeInfo(primitive, sqlType, sqlTypeName)); } } /** Iterator that never returns any elements. */ private static class EmptyIterator implements Iterator { public static final Iterator INSTANCE = new EmptyIterator(); public void remove() { throw new UnsupportedOperationException(); } public boolean hasNext() { return false; } public Object next() { throw new NoSuchElementException(); } } /** Iterable that yields an iterator over rows coming from a sequence of * {@link Meta.Frame}s. */ private class FetchIterable implements Iterable { private final AvaticaStatement stmt; private final QueryState state; private final Frame firstFrame; private final List parameterValues; public FetchIterable(AvaticaStatement stmt, QueryState state, Frame firstFrame, List parameterValues) { this.stmt = stmt; this.state = state; this.firstFrame = firstFrame; this.parameterValues = parameterValues; } public Iterator iterator() { return new FetchIterator(stmt, state, firstFrame, parameterValues); } } /** Iterator over rows coming from a sequence of {@link Meta.Frame}s. */ private class FetchIterator implements Iterator { private final AvaticaStatement stmt; private final QueryState state; private Frame frame; private Iterator rows; private List parameterValues; private List originalParameterValues; private long currentOffset = 0; public FetchIterator(AvaticaStatement stmt, QueryState state, Frame firstFrame, List parameterValues) { this.stmt = stmt; this.state = state; this.parameterValues = parameterValues; this.originalParameterValues = parameterValues; if (firstFrame == null) { frame = Frame.MORE; rows = EmptyIterator.INSTANCE; } else { frame = firstFrame; rows = firstFrame.rows.iterator(); } moveNext(); } public void remove() { throw new UnsupportedOperationException("remove"); } public boolean hasNext() { return rows != null; } public Object next() { if (rows == null) { throw new NoSuchElementException(); } final Object o = rows.next(); currentOffset++; moveNext(); return o; } private void moveNext() { for (;;) { if (rows.hasNext()) { break; } if (frame.done) { rows = null; break; } try { // currentOffset updated after element is read from `rows` iterator frame = fetch(stmt.handle, currentOffset, AvaticaStatement.DEFAULT_FETCH_SIZE); } catch (NoSuchStatementException e) { resetStatement(); // re-fetch the batch where we left off continue; } catch (MissingResultsException e) { try { // We saw the statement, but it didnt' have a resultset initialized. So, reset it. if (!stmt.syncResults(state, currentOffset)) { // This returned false, so there aren't actually any more results to iterate over frame = null; rows = null; break; } // syncResults returning true means we need to fetch those results } catch (NoSuchStatementException e1) { // Tried to reset the result set, but lost the statement, save a loop before retrying. resetStatement(); // Will just loop back around to a MissingResultsException, but w/e recursion } // Kick back to the top to try to fetch again (in both branches) continue; } parameterValues = null; // don't execute next time if (frame == null) { rows = null; break; } // It is valid for rows to be empty, so we go around the loop again to // check rows = frame.rows.iterator(); } } private void resetStatement() { // If we have to reset the statement, we need to reset the parameterValues too parameterValues = originalParameterValues; // Defer to the statement to reset itself stmt.resetStatement(); } } /** Returns whether a list of parameter values has any null elements. */ public static boolean checkParameterValueHasNull(List parameterValues) { for (TypedValue x : parameterValues) { if (x == null) { return true; } } return false; } } // End MetaImpl.java