001: /* Copyright (c) 2001-2005, The HSQL Development Group
002: * All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * Redistributions of source code must retain the above copyright notice, this
008: * list of conditions and the following disclaimer.
009: *
010: * Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * Neither the name of the HSQL Development Group nor the names of its
015: * contributors may be used to endorse or promote products derived from this
016: * software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
022: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
026: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package org.hsqldb;
032:
033: import org.hsqldb.HsqlNameManager.HsqlName;
034: import org.hsqldb.lib.HsqlArrayList;
035: import org.hsqldb.lib.Iterator;
036:
037: // fredt@users 20020420 - patch523880 by leptipre@users - VIEW support - modified
038: // fredt@users 20031227 - remimplementated as compiled query
039:
040: /**
041: * Represents an SQL VIEW based on a SELECT statement.
042: *
043: * @author leptipre@users
044: * @author fredt@users
045: * @version 1.8.0
046: * @since 1.7.0
047: */
048: class View extends Table {
049:
050: Select viewSelect;
051: SubQuery viewSubQuery;
052: private String statement;
053: private HsqlName[] colList;
054:
055: /** schema at the time of compilation */
056: HsqlName compileTimeSchema;
057:
058: /**
059: * List of subqueries in this view in order of materialization. Last
060: * element is the view itself.
061: */
062: SubQuery[] viewSubqueries;
063:
064: /**
065: * Constructor.
066: * @param Session
067: * @param db database
068: * @param name HsqlName of the view
069: * @param definition SELECT statement of the view
070: * @param columns array of HsqlName column names
071: * @throws HsqlException
072: */
073: View(Session session, Database db, HsqlName name,
074: String definition, HsqlName[] columns) throws HsqlException {
075:
076: super (db, name, VIEW);
077:
078: isReadOnly = true;
079: colList = columns;
080: statement = trimStatement(definition);
081: compileTimeSchema = session.getSchemaHsqlName(null);
082:
083: compile(session);
084: replaceAsterisksInStatement();
085:
086: HsqlName[] schemas = getSchemas();
087:
088: for (int i = 0; i < schemas.length; i++) {
089: if (db.schemaManager.isSystemSchema(schemas[i])) {
090: continue;
091: }
092:
093: if (!schemas[i].equals(name.schema)) {
094: throw Trace
095: .error(Trace.INVALID_SCHEMA_NAME_NO_SUBCLASS);
096: }
097: }
098: }
099:
100: /**
101: * Returns the SELECT statement trimmed of any terminating SQL
102: * whitespace, separators or SQL comments.
103: */
104: static String trimStatement(String s) throws HsqlException {
105:
106: int position;
107: String str;
108: Tokenizer tokenizer = new Tokenizer(s);
109:
110: // fredt@users - this establishes the end of the actual statement
111: // to get rid of any end semicolon or comment line after the end
112: // of statement
113: do {
114: position = tokenizer.getPosition();
115: str = tokenizer.getString();
116: } while (str.length() != 0 || tokenizer.wasValue());
117:
118: return s.substring(0, position).trim();
119: }
120:
121: /**
122: * Compiles the SELECT statement and sets up the columns.
123: */
124: void compile(Session session) throws HsqlException {
125:
126: // create the working table
127: Parser p = new Parser(session, this .database, new Tokenizer(
128: statement));
129:
130: p.setCompilingView();
131:
132: int brackets = p.parseOpenBracketsSelect();
133:
134: viewSubQuery = p.parseSubquery(brackets, colList, true,
135: Expression.VIEW);
136:
137: p.setAsView(this );
138:
139: viewSubqueries = p.getSortedSubqueries();
140: viewSelect = viewSubQuery.select;
141:
142: viewSelect.prepareResult(session);
143:
144: Result.ResultMetaData metadata = viewSelect.resultMetaData;
145: int columns = viewSelect.iResultLen;
146:
147: if (super .columnCount == 0) {
148:
149: // do not add columns at recompile time
150: super .addColumns(metadata, columns);
151: }
152: }
153:
154: /**
155: * Returns the SELECT statement for the view.
156: */
157: String getStatement() {
158: return statement;
159: }
160:
161: /**
162: * is a private helper for replaceAsterisksInStatement, to avoid some code duplication
163: */
164: private void collectAsteriskPos(final Select select,
165: HsqlArrayList asteriskPositions) {
166:
167: if (select.asteriskPositions == null) {
168: return;
169: }
170:
171: Iterator asterisks = select.asteriskPositions.keySet()
172: .iterator();
173:
174: while (asterisks.hasNext()) {
175: int pos = asterisks.nextInt();
176:
177: asteriskPositions.set(pos, select.asteriskPositions
178: .get(pos));
179: }
180: }
181:
182: /**
183: * replaces all asterisks in our statement with the actual column list
184: *
185: * This way, we ensure what is required by the standard: a view returns a result
186: * which reflects the structure of the underlying tables at the *time of the definition
187: * of the view.
188: */
189: private void replaceAsterisksInStatement() {
190:
191: HsqlArrayList asteriskPositions = new HsqlArrayList();
192:
193: asteriskPositions.setSize(statement.length());
194:
195: // asterisk positions in sub queries
196: for (int i = 0; i < viewSubqueries.length; ++i) {
197:
198: // collect the occurances of asterisks in the statement
199: Select subSelect = viewSubqueries[i].select;
200:
201: collectAsteriskPos(subSelect, asteriskPositions);
202:
203: // the same for all (possible) UNION SELECTs of the sub select
204: if (subSelect.unionArray != null) {
205:
206: // start with index 1, not 0 - the first select is the one already covered by subSelect
207: for (int u = 1; u < subSelect.unionArray.length; ++u) {
208: collectAsteriskPos(subSelect.unionArray[u],
209: asteriskPositions);
210: }
211: }
212: }
213:
214: StringBuffer expandedStatement = new StringBuffer(statement);
215:
216: for (int pos = asteriskPositions.size() - 1; pos >= 0; --pos) {
217: String colList = (String) asteriskPositions.get(pos);
218:
219: if (colList == null) {
220: continue;
221: }
222:
223: expandedStatement.replace(pos, expandedStatement.indexOf(
224: "*", pos) + 1, colList);
225: }
226:
227: statement = expandedStatement.toString();
228: }
229:
230: /**
231: * Overridden to disable SET TABLE READONLY DDL for View objects.
232: */
233: void setDataReadOnly(boolean value) throws HsqlException {
234: throw Trace.error(Trace.NOT_A_TABLE);
235: }
236:
237: /**
238: * Returns list of schemas
239: */
240: HsqlName[] getSchemas() {
241:
242: HsqlArrayList list = new HsqlArrayList();
243:
244: for (int i = 0; i < viewSubqueries.length; i++) {
245: Select select = viewSubqueries[i].select;
246:
247: for (; select != null; select = select.unionSelect) {
248: TableFilter[] tfilter = select.tFilter;
249:
250: for (int j = 0; j < tfilter.length; j++) {
251: list.add(tfilter[j].filterTable.tableName.schema);
252: }
253: }
254: }
255:
256: return (HsqlName[]) list.toArray(new HsqlName[list.size()]);
257: }
258:
259: boolean hasView(View view) {
260:
261: if (view == this ) {
262: return false;
263: }
264:
265: for (int i = 0; i < viewSubqueries.length; i++) {
266: if (viewSubqueries[i].view == view) {
267: return true;
268: }
269: }
270:
271: return false;
272: }
273:
274: /**
275: * Returns true if the view references any column of the named table.
276: */
277: boolean hasTable(Table table) {
278:
279: for (int i = 0; i < viewSubqueries.length; i++) {
280: Select select = viewSubqueries[i].select;
281:
282: for (; select != null; select = select.unionSelect) {
283: TableFilter[] tfilter = select.tFilter;
284:
285: for (int j = 0; j < tfilter.length; j++) {
286: if (table.equals(tfilter[j].filterTable.tableName)) {
287: return true;
288: }
289: }
290: }
291: }
292:
293: return false;
294: }
295:
296: /**
297: * Returns true if the view references the named column of the named table,
298: * otherwise false.
299: */
300: boolean hasColumn(Table table, String colname) {
301:
302: if (hasTable(table)) {
303: Expression.Collector coll = new Expression.Collector();
304:
305: coll.addAll(
306: viewSubqueries[viewSubqueries.length - 1].select,
307: Expression.COLUMN);
308:
309: Iterator it = coll.iterator();
310:
311: for (; it.hasNext();) {
312: Expression e = (Expression) it.next();
313:
314: if (colname.equals(e.getBaseColumnName())
315: && table.equals(e.getTableHsqlName())) {
316: return true;
317: }
318: }
319: }
320:
321: return false;
322: }
323:
324: /**
325: * Returns true if the view references the named SEQUENCE,
326: * otherwise false.
327: */
328: boolean hasSequence(NumberSequence sequence) {
329:
330: Expression.Collector coll = new Expression.Collector();
331:
332: coll.addAll(viewSubqueries[viewSubqueries.length - 1].select,
333: Expression.SEQUENCE);
334:
335: Iterator it = coll.iterator();
336:
337: for (; it.hasNext();) {
338: Expression e = (Expression) it.next();
339:
340: if (e.valueData == sequence) {
341: return true;
342: }
343: }
344:
345: return false;
346: }
347: }
|