/* Copyright (c) 2001-2008, The HSQL Development Group * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the HSQL Development Group nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG, * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.hsqldb; import org.hsqldb.HsqlNameManager.HsqlName; import org.hsqldb.lib.HsqlArrayList; import org.hsqldb.lib.Iterator; import org.hsqldb.lib.IntKeyHashMap; import org.hsqldb.lib.ArrayUtil; // fredt@users 20020420 - patch523880 by leptipre@users - VIEW support - modified // fredt@users 20031227 - remimplementated as compiled query /** * Represents an SQL VIEW based on a SELECT statement. * * @author leptipre@users * @author fredt@users * @version 1.8.0 * @since 1.7.0 */ class View extends Table { Select viewSelect; SubQuery viewSubQuery; private String statement; private HsqlName[] colList; /** schema at the time of compilation */ HsqlName compileTimeSchema; /** * List of subqueries in this view in order of materialization. Last * element is the view itself. */ SubQuery[] viewSubqueries; /** * Constructor. * @param Session * @param db database * @param name HsqlName of the view * @param definition SELECT statement of the view * @param columns array of HsqlName column names * @throws HsqlException */ View(Session session, Database db, HsqlName name, String definition, HsqlName[] columns) throws HsqlException { super(db, name, VIEW); isReadOnly = true; colList = columns; statement = trimStatement(definition); compileTimeSchema = session.getSchemaHsqlName(null); compile(session); replaceAsterisksInStatement(); HsqlName[] schemas = getSchemas(); for (int i = 0; i < schemas.length; i++) { if (db.schemaManager.isSystemSchema(schemas[i])) { continue; } if (!schemas[i].equals(name.schema)) { throw Trace.error(Trace.INVALID_SCHEMA_NAME_NO_SUBCLASS); } } } /** * Returns the SELECT statement trimmed of any terminating SQL * whitespace, separators or SQL comments. */ static String trimStatement(String s) throws HsqlException { int position; String str; Tokenizer tokenizer = new Tokenizer(s); // fredt@users - this establishes the end of the actual statement // to get rid of any end semicolon or comment line after the end // of statement do { position = tokenizer.getPosition(); str = tokenizer.getString(); } while (str.length() != 0 || tokenizer.wasValue()); return s.substring(0, position).trim(); } /** * Compiles the SELECT statement and sets up the columns. */ void compile(Session session) throws HsqlException { // create the working table Parser p = new Parser(session, this.database, new Tokenizer(statement)); p.setCompilingView(); int brackets = p.parseOpenBracketsSelect(); viewSubQuery = p.parseSubquery(brackets, colList, true, Expression.VIEW); p.setAsView(this); viewSubqueries = p.getSortedSubqueries(); viewSelect = viewSubQuery.select; viewSelect.prepareResult(session); Result.ResultMetaData metadata = viewSelect.resultMetaData; int columns = viewSelect.iResultLen; if (super.columnCount == 0) { // do not add columns at recompile time super.addColumns(metadata, columns); } } /** * Returns the SELECT statement for the view. */ String getStatement() { return statement; } /** * is a private helper for replaceAsterisksInStatement, to avoid some code duplication */ private void collectAsteriskPos(final Select select, IntKeyHashMap asteriskPositions) { if (select.asteriskPositions == null) { return; } Iterator asterisks = select.asteriskPositions.keySet().iterator(); while (asterisks.hasNext()) { int pos = asterisks.nextInt(); asteriskPositions.put(pos, select.asteriskPositions.get(pos)); } // The following is to guarantee the invariant of this class, that the |astersiskPositions| // member of the various Select instances properly describe the occurances // of Expression.ASTERISK in the statement. // Since we are going to replace all those asterisks, we also need to reset the various // |astersiskPositions| instances, which is best done here were all non-null // Select's need to pass by. select.asteriskPositions = null; } /** * replaces all asterisks in our statement with the actual column list * * This way, we ensure what is required by the standard: a view returns a result * which reflects the structure of the underlying tables at the *time of the definition * of the view. */ private void replaceAsterisksInStatement() throws HsqlException { IntKeyHashMap asteriskPositions = new IntKeyHashMap(); // asterisk positions in sub queries for (int i = 0; i < viewSubqueries.length; ++i) { // collect the occurances of asterisks in the statement Select subSelect = viewSubqueries[i].select; collectAsteriskPos(subSelect, asteriskPositions); // the same for all (possible) UNION SELECTs of the sub select if (subSelect.unionArray != null) { // start with index 1, not 0 - the first select is the one already covered by subSelect for (int u = 1; u < subSelect.unionArray.length; ++u) { collectAsteriskPos(subSelect.unionArray[u], asteriskPositions); } } } int[] positions = new int[asteriskPositions.size()]; Iterator it = asteriskPositions.keySet().iterator(); for (int i = 0; i < positions.length; i++) { positions[i] = it.nextInt(); } ArrayUtil.sortArray(positions); StringBuffer expandedStatement = new StringBuffer(); int lastPos = 0; String segment; for (int i = 0; i < positions.length; i++) { int pos = positions[i]; String colList = (String) asteriskPositions.get(pos); if (colList == null) { continue; } segment = statement.substring(lastPos, pos); lastPos = statement.indexOf("*", pos) + 1; expandedStatement.append(segment); expandedStatement.append(' '); expandedStatement.append(colList); expandedStatement.append(' '); } segment = statement.substring(lastPos, statement.length()); expandedStatement.append(segment); statement = expandedStatement.toString(); } /** * Overridden to disable SET TABLE READONLY DDL for View objects. */ void setDataReadOnly(boolean value) throws HsqlException { throw Trace.error(Trace.NOT_A_TABLE); } /** * Returns list of schemas */ HsqlName[] getSchemas() { HsqlArrayList list = new HsqlArrayList(); for (int i = 0; i < viewSubqueries.length; i++) { Select select = viewSubqueries[i].select; for (; select != null; select = select.unionSelect) { TableFilter[] tfilter = select.tFilter; for (int j = 0; j < tfilter.length; j++) { list.add(tfilter[j].filterTable.tableName.schema); } } } return (HsqlName[]) list.toArray(new HsqlName[list.size()]); } boolean hasView(View view) { if (view == this) { return false; } for (int i = 0; i < viewSubqueries.length; i++) { if (viewSubqueries[i].view == view) { return true; } } return false; } /** * Returns true if the view references any column of the named table. */ boolean hasTable(Table table) { for (int i = 0; i < viewSubqueries.length; i++) { Select select = viewSubqueries[i].select; for (; select != null; select = select.unionSelect) { TableFilter[] tfilter = select.tFilter; for (int j = 0; j < tfilter.length; j++) { if (table.equals(tfilter[j].filterTable.tableName)) { return true; } } } } return false; } /** * Returns true if the view references the named column of the named table, * otherwise false. */ boolean hasColumn(Table table, String colname) { if (hasTable(table)) { Expression.Collector coll = new Expression.Collector(); coll.addAll(viewSubqueries[viewSubqueries.length - 1].select, Expression.COLUMN); Iterator it = coll.iterator(); for (; it.hasNext(); ) { Expression e = (Expression) it.next(); if (colname.equals(e.getBaseColumnName()) && table.equals(e.getTableHsqlName())) { return true; } } } return false; } /** * Returns true if the view references the named SEQUENCE, * otherwise false. */ boolean hasSequence(NumberSequence sequence) { Expression.Collector coll = new Expression.Collector(); coll.addAll(viewSubqueries[viewSubqueries.length - 1].select, Expression.SEQUENCE); Iterator it = coll.iterator(); for (; it.hasNext(); ) { Expression e = (Expression) it.next(); if (e.valueData == sequence) { return true; } } return false; } }