/* * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, * and the EPL 1.0 (http://h2database.com/html/license.html). * Initial Developer: H2 Group */ package org.h2.table; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import org.h2.api.ErrorCode; import org.h2.engine.Session; import org.h2.expression.Expression; import org.h2.expression.FunctionCall; import org.h2.expression.TableFunction; import org.h2.index.FunctionIndex; import org.h2.index.Index; import org.h2.index.IndexType; import org.h2.message.DbException; import org.h2.result.LocalResult; import org.h2.result.ResultInterface; import org.h2.result.Row; import org.h2.schema.Schema; import org.h2.value.DataType; import org.h2.value.Value; import org.h2.value.ValueNull; import org.h2.value.ValueResultSet; /** * A table backed by a system or user-defined function that returns a result * set. */ public class FunctionTable extends Table { private final FunctionCall function; private final long rowCount; private Expression functionExpr; private LocalResult cachedResult; private Value cachedValue; //在org.h2.command.Parser.parseValuesTable()和org.h2.command.Parser.readTableFilter(boolean)中有调用 //functionExpr和function实际上是同一个对象 public FunctionTable(Schema schema, Session session, Expression functionExpr, FunctionCall function) { super(schema, 0, function.getName(), false, true); this.functionExpr = functionExpr; this.function = function; if (function instanceof TableFunction) { rowCount = ((TableFunction) function).getRowCount(); } else { rowCount = Long.MAX_VALUE; } function.optimize(session); int type = function.getType(); if (type != Value.RESULT_SET) { throw DbException.get( ErrorCode.FUNCTION_MUST_RETURN_RESULT_SET_1, function.getName()); } Expression[] args = function.getArgs(); int numParams = args.length; Expression[] columnListArgs = new Expression[numParams]; for (int i = 0; i < numParams; i++) { args[i] = args[i].optimize(session); columnListArgs[i] = args[i]; } ValueResultSet template = function.getValueForColumnList( session, columnListArgs); if (template == null) { throw DbException.get( ErrorCode.FUNCTION_MUST_RETURN_RESULT_SET_1, function.getName()); } ResultSet rs = template.getResultSet(); try { ResultSetMetaData meta = rs.getMetaData(); int columnCount = meta.getColumnCount(); Column[] cols = new Column[columnCount]; for (int i = 0; i < columnCount; i++) { cols[i] = new Column(meta.getColumnName(i + 1), DataType.getValueTypeFromResultSet(meta, i + 1), meta.getPrecision(i + 1), meta.getScale(i + 1), meta.getColumnDisplaySize(i + 1)); } setColumns(cols); } catch (SQLException e) { throw DbException.convert(e); } } @Override public boolean lock(Session session, boolean exclusive, boolean forceLockEvenInMvcc) { // nothing to do return false; } @Override public void close(Session session) { // nothing to do } @Override public void unlock(Session s) { // nothing to do } @Override public boolean isLockedExclusively() { return false; } @Override public Index addIndex(Session session, String indexName, int indexId, IndexColumn[] cols, IndexType indexType, boolean create, String indexComment) { throw DbException.getUnsupportedException("ALIAS"); } @Override public void removeRow(Session session, Row row) { throw DbException.getUnsupportedException("ALIAS"); } @Override public void truncate(Session session) { throw DbException.getUnsupportedException("ALIAS"); } @Override public boolean canDrop() { throw DbException.throwInternalError(toString()); } @Override public void addRow(Session session, Row row) { throw DbException.getUnsupportedException("ALIAS"); } @Override public void checkSupportAlter() { throw DbException.getUnsupportedException("ALIAS"); } @Override public TableType getTableType() { return null; } @Override public Index getScanIndex(Session session) { return new FunctionIndex(this, IndexColumn.wrap(columns)); } @Override public ArrayList<Index> getIndexes() { return null; } @Override public boolean canGetRowCount() { return rowCount != Long.MAX_VALUE; } @Override public long getRowCount(Session session) { return rowCount; } @Override public String getCreateSQL() { return null; } @Override public String getDropSQL() { return null; } @Override public void checkRename() { throw DbException.getUnsupportedException("ALIAS"); } /** * Read the result from the function. This method buffers the result in a * temporary file. * * @param session the session * @return the result */ public ResultInterface getResult(Session session) { ValueResultSet v = getValueResultSet(session); if (v == null) { return null; } if (cachedResult != null && cachedValue == v) { cachedResult.reset(); return cachedResult; } LocalResult result = LocalResult.read(session, v.getResultSet(), 0); if (function.isDeterministic()) { cachedResult = result; cachedValue = v; } return result; } /** * Read the result set from the function. This method doesn't cache. * * @param session the session * @return the result set */ public ResultSet getResultSet(Session session) { ValueResultSet v = getValueResultSet(session); return v == null ? null : v.getResultSet(); } private ValueResultSet getValueResultSet(Session session) { functionExpr = functionExpr.optimize(session); Value v = functionExpr.getValue(session); if (v == ValueNull.INSTANCE) { return null; } return (ValueResultSet) v; } public boolean isBufferResultSetToLocalTemp() { return function.isBufferResultSetToLocalTemp(); } @Override public long getMaxDataModificationId() { // TODO optimization: table-as-a-function currently doesn't know the // last modified date return Long.MAX_VALUE; } @Override public Index getUniqueIndex() { return null; } @Override public String getSQL() { return function.getSQL(); } @Override public long getRowCountApproximation() { return rowCount; } @Override public long getDiskSpaceUsed() { return 0; } @Override public boolean isDeterministic() { return function.isDeterministic(); } @Override public boolean canReference() { return false; } }