001: /*
002:
003: Derby - Class org.apache.derby.impl.sql.compile.FromSubquery
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.impl.sql.compile;
023:
024: import org.apache.derby.iapi.reference.SQLState;
025:
026: import org.apache.derby.iapi.services.context.ContextManager;
027:
028: import org.apache.derby.iapi.error.StandardException;
029: import org.apache.derby.iapi.sql.compile.CompilerContext;
030: import org.apache.derby.iapi.sql.compile.C_NodeTypes;
031: import org.apache.derby.iapi.sql.dictionary.DataDictionary;
032:
033: import org.apache.derby.iapi.services.sanity.SanityManager;
034:
035: import org.apache.derby.iapi.util.JBitSet;
036:
037: import java.util.Properties;
038:
039: /**
040: * A FromSubquery represents a subquery in the FROM list of a DML statement.
041: *
042: * The current implementation of this class is only
043: * sufficient for Insert's need to push a new
044: * select on top of the one the user specified,
045: * to make the selected structure match that
046: * of the insert target table.
047: *
048: * @author Jeff Lichtman
049: */
050: public class FromSubquery extends FromTable {
051: boolean generatedForGroupByClause;
052: boolean generatedForHavingClause;
053: ResultSetNode subquery;
054:
055: /**
056: * Intializer for a table in a FROM list.
057: *
058: * @param subquery The subquery
059: * @param correlationName The correlation name
060: * @param derivedRCL The derived column list
061: * @param tableProperties Properties list associated with the table
062: */
063: public void init(Object subquery, Object correlationName,
064: Object derivedRCL, Object tableProperties) {
065: super .init(correlationName, tableProperties);
066: this .subquery = (ResultSetNode) subquery;
067: resultColumns = (ResultColumnList) derivedRCL;
068: }
069:
070: /**
071: * Convert this object to a String. See comments in QueryTreeNode.java
072: * for how this should be done for tree printing.
073: *
074: * @return This object as a String
075: */
076:
077: public String toString() {
078: if (SanityManager.DEBUG) {
079: return "generatedForGroupByClause: "
080: + generatedForGroupByClause + "\n"
081: + "generatedForHavingClause: "
082: + generatedForHavingClause + "\n"
083: + super .toString();
084: } else {
085: return "";
086: }
087: }
088:
089: /**
090: * Prints the sub-nodes of this object. See QueryTreeNode.java for
091: * how tree printing is supposed to work.
092: *
093: * @param depth The depth of this node in the tree
094: */
095:
096: public void printSubNodes(int depth) {
097: if (SanityManager.DEBUG) {
098: super .printSubNodes(depth);
099:
100: if (subquery != null) {
101: printLabel(depth, "subquery: ");
102: subquery.treePrint(depth + 1);
103: }
104: }
105: }
106:
107: /**
108: * Return the "subquery" from this node.
109: *
110: * @return ResultSetNode The "subquery" from this node.
111: */
112: public ResultSetNode getSubquery() {
113: return subquery;
114: }
115:
116: /**
117: * Mark this FromSubquery as being generated for a GROUP BY clause.
118: * (This node represents the SELECT thru GROUP BY clauses. We
119: * appear in the FromList of a SelectNode generated to represent
120: * the result of the GROUP BY. This allows us to add ResultColumns
121: * to the SelectNode for the user's query.
122: */
123: public void markAsForGroupByClause() {
124: generatedForGroupByClause = true;
125: }
126:
127: /**
128: * Mark this FromSubquery as being generated for a HAVING clause.
129: * (This node represents the SELECT thru GROUP BY clauses. We
130: * appear in the FromList of a SelectNode generated to represent
131: * the actual HAVING clause.
132: */
133: public void markAsForHavingClause() {
134: generatedForHavingClause = true;
135: }
136:
137: /**
138: * Determine whether or not the specified name is an exposed name in
139: * the current query block.
140: *
141: * @param name The specified name to search for as an exposed name.
142: * @param schemaName Schema name, if non-null.
143: * @param exactMatch Whether or not we need an exact match on specified schema and table
144: * names or match on table id.
145: *
146: * @return The FromTable, if any, with the exposed name.
147: *
148: * @exception StandardException Thrown on error
149: */
150: protected FromTable getFromTableByName(String name,
151: String schemaName, boolean exactMatch)
152: throws StandardException {
153: if (generatedForGroupByClause || generatedForHavingClause) {
154: return subquery.getFromTableByName(name, schemaName,
155: exactMatch);
156: } else {
157: return super .getFromTableByName(name, schemaName,
158: exactMatch);
159: }
160: }
161:
162: /**
163: * Bind this subquery that appears in the FROM list.
164: *
165: * @param dataDictionary The DataDictionary to use for binding
166: * @param fromListParam FromList to use/append to.
167: *
168: * @return ResultSetNode The bound FromSubquery.
169: *
170: * @exception StandardException Thrown on error
171: */
172:
173: public ResultSetNode bindNonVTITables(
174: DataDictionary dataDictionary, FromList fromListParam)
175: throws StandardException {
176: /* Assign the tableNumber */
177: if (tableNumber == -1) // allow re-bind, in which case use old number
178: tableNumber = getCompilerContext().getNextTableNumber();
179:
180: subquery = subquery.bindNonVTITables(dataDictionary,
181: fromListParam);
182:
183: return this ;
184: }
185:
186: /**
187: * Bind this subquery that appears in the FROM list.
188: *
189: * @param fromListParam FromList to use/append to.
190: *
191: * @return ResultSetNode The bound FromSubquery.
192: *
193: * @exception StandardException Thrown on error
194: */
195:
196: public ResultSetNode bindVTITables(FromList fromListParam)
197: throws StandardException {
198: subquery = subquery.bindVTITables(fromListParam);
199:
200: return this ;
201: }
202:
203: /**
204: * Check for (and reject) ? parameters directly under the ResultColumns.
205: * This is done for SELECT statements. For FromSubquery, we
206: * simply pass the check through to the subquery.
207: *
208: * @exception StandardException Thrown if a ? parameter found
209: * directly under a ResultColumn
210: */
211:
212: public void rejectParameters() throws StandardException {
213: subquery.rejectParameters();
214: }
215:
216: /**
217: * Bind the expressions in this FromSubquery. This means
218: * binding the sub-expressions, as well as figuring out what the return
219: * type is for each expression.
220: *
221: * @exception StandardException Thrown on error
222: */
223:
224: public void bindExpressions(FromList fromListParam)
225: throws StandardException {
226: FromList emptyFromList = (FromList) getNodeFactory().getNode(
227: C_NodeTypes.FROM_LIST,
228: getNodeFactory().doJoinOrderOptimization(),
229: getContextManager());
230: ResultColumnList derivedRCL = resultColumns;
231: ResultColumnList subqueryRCL;
232: FromList nestedFromList;
233:
234: /* From subqueries cannot be correlated, so we pass an empty FromList
235: * to subquery.bindExpressions() and .bindResultColumns(). However,
236: * the parser rewrites queries which have GROUP BY and HAVING clauses.
237: * For these rewritten pseudo-subqueries, we need to pass in the outer FromList
238: * which contains correlated tables.
239: */
240: if (generatedForGroupByClause || generatedForHavingClause) {
241: nestedFromList = fromListParam;
242: } else {
243: nestedFromList = emptyFromList;
244: }
245:
246: subquery.bindExpressions(nestedFromList);
247: subquery.bindResultColumns(nestedFromList);
248:
249: /* Now that we've bound the expressions in the subquery, we
250: * can propagate the subquery's RCL up to the FromSubquery.
251: * Get the subquery's RCL, assign shallow copy back to
252: * it and create new VirtualColumnNodes for the original's
253: * ResultColumn.expressions.
254: * NOTE: If the size of the derived column list is less than
255: * the size of the subquery's RCL and the derived column list is marked
256: * for allowing a size mismatch, then we have a select * view
257: * on top of a table that has had columns added to it via alter table.
258: * In this case, we trim out the columns that have been added to
259: * the table since the view was created.
260: */
261: subqueryRCL = subquery.getResultColumns();
262: if (resultColumns != null
263: && resultColumns.getCountMismatchAllowed()
264: && resultColumns.size() < subqueryRCL.size()) {
265: for (int index = subqueryRCL.size() - 1; index >= resultColumns
266: .size(); index--) {
267: subqueryRCL.removeElementAt(index);
268: }
269: }
270:
271: subquery.setResultColumns(subqueryRCL.copyListAndObjects());
272: subqueryRCL.genVirtualColumnNodes(subquery, subquery
273: .getResultColumns());
274: resultColumns = subqueryRCL;
275:
276: /* Propagate the name info from the derived column list */
277: if (derivedRCL != null) {
278: resultColumns.propagateDCLInfo(derivedRCL, correlationName);
279: }
280: }
281:
282: /**
283: * Try to find a ResultColumn in the table represented by this FromBaseTable
284: * that matches the name in the given ColumnReference.
285: *
286: * @param columnReference The columnReference whose name we're looking
287: * for in the given table.
288: *
289: * @return A ResultColumn whose expression is the ColumnNode
290: * that matches the ColumnReference.
291: * Returns null if there is no match.
292: *
293: * @exception StandardException Thrown on error
294: */
295:
296: public ResultColumn getMatchingColumn(
297: ColumnReference columnReference) throws StandardException {
298: ResultColumn resultColumn = null;
299: String columnsTableName;
300:
301: /*
302: ** RESOLVE: When we add support for schemas, check to see if
303: ** the column name specifies a schema, and if so, if this
304: ** table is in that schema.
305: */
306:
307: columnsTableName = columnReference.getTableName();
308:
309: /* We have 5 cases here:
310: * 1. ColumnReference was generated to replace an aggregate.
311: * (We are the wrapper for a HAVING clause and the ColumnReference
312: * was generated to reference the aggregate which was pushed down into
313: * the SELECT list in the user's query.)
314: * Just do what you would expect. Try to resolve the
315: * ColumnReference against our RCL if the ColumnReference is unqualified
316: * or if it is qualified with our exposed name.
317: * 2. We are the wrapper for a GROUP BY and a HAVING clause and
318: * either the ColumnReference is qualified or it is in
319: * the HAVING clause. For example:
320: * select a from t1 group by a having t1.a = 1
321: * select a as asdf from t1 group by a having a = 1
322: * We need to match against the underlying FromList and then find
323: * the grandparent ResultColumn in our RCL so that we return a
324: * ResultColumn from the correct ResultSetNode. It is okay not to
325: * find a matching grandparent node. In fact, this is how we ensure
326: * the correct semantics for ColumnReferences in the HAVING clause
327: * (which must be bound against the GROUP BY list.)
328: * 3. We are the wrapper for a HAVING clause without a GROUP BY and
329: * the ColumnReference is from the HAVING clause. ColumnReferences
330: * are invalid in this case, so we return null.
331: * 4. We are the wrapper for a GROUP BY with no HAVING. This has
332: * to be a separate case because of #5 and the following query:
333: * select * from (select c1 from t1) t, (select c1 from t1) tt
334: * group by t1.c1, tt.c1
335: * (The correlation names are lost in the generated FromSuquery.)
336: * 5. Everything else - do what you would expect. Try to resolve the
337: * ColumnReference against our RCL if the ColumnReference is unqualified
338: * or if it is qualified with our exposed name.
339: */
340: if (columnReference.getGeneratedToReplaceAggregate()) // 1
341: {
342: resultColumn = resultColumns
343: .getResultColumn(columnReference.getColumnName());
344: } else if (generatedForGroupByClause
345: && generatedForHavingClause
346: && (columnsTableName != null || columnReference
347: .getClause() != ValueNode.IN_SELECT_LIST)) // 2
348: {
349: if (SanityManager.DEBUG) {
350: SanityManager.ASSERT(correlationName == null,
351: "correlationName expected to be null");
352: SanityManager.ASSERT(subquery instanceof SelectNode,
353: "subquery expected to be instanceof SelectNode, not "
354: + subquery.getClass().getName());
355: }
356:
357: SelectNode select = (SelectNode) subquery;
358:
359: resultColumn = select.getFromList().bindColumnReference(
360: columnReference);
361:
362: /* Find and return the matching RC from our RCL.
363: * (Not an error if no match found. Let ColumnReference deal with it.
364: */
365: if (resultColumn != null) {
366: /* Is there a matching resultColumn in the subquery's RCL? */
367: resultColumn = subquery.getResultColumns()
368: .findParentResultColumn(resultColumn);
369: if (resultColumn != null) {
370: /* Is there a matching resultColumn in our RCL? */
371: resultColumn = resultColumns
372: .findParentResultColumn(resultColumn);
373: }
374: }
375: } else if ((generatedForHavingClause && !generatedForGroupByClause) // 3
376: && (columnReference.getClause() != ValueNode.IN_SELECT_LIST)) {
377: resultColumn = null;
378: } else if (generatedForGroupByClause) // 4
379: {
380: resultColumn = resultColumns.getResultColumn(
381: columnsTableName, columnReference.getColumnName());
382: } else if (columnsTableName == null
383: || columnsTableName.equals(correlationName)) // 5?
384: {
385: resultColumn = resultColumns.getAtMostOneResultColumn(
386: columnReference, correlationName);
387: }
388:
389: if (resultColumn != null) {
390: columnReference.setTableNumber(tableNumber);
391: }
392:
393: return resultColumn;
394: }
395:
396: /**
397: * Preprocess a ResultSetNode - this currently means:
398: * o Generating a referenced table map for each ResultSetNode.
399: * o Putting the WHERE and HAVING clauses in conjunctive normal form (CNF).
400: * o Converting the WHERE and HAVING clauses into PredicateLists and
401: * classifying them.
402: * o Ensuring that a ProjectRestrictNode is generated on top of every
403: * FromBaseTable and generated in place of every FromSubquery.
404: * o Pushing single table predicates down to the new ProjectRestrictNodes.
405: *
406: * @param numTables The number of tables in the DML Statement
407: * @param gbl The group by list, if any
408: * @param fromList The from list, if any
409: *
410: * @return ResultSetNode at top of preprocessed tree.
411: *
412: * @exception StandardException Thrown on error
413: */
414:
415: public ResultSetNode preprocess(int numTables, GroupByList gbl,
416: FromList fromList) throws StandardException {
417: /* We want to chop out the FromSubquery from the tree and replace it
418: * with a ProjectRestrictNode. One complication is that there may be
419: * ColumnReferences above us which point to the FromSubquery's RCL.
420: * What we want to return is a tree with a PRN with the
421: * FromSubquery's RCL on top. (In addition, we don't want to be
422: * introducing any redundant ProjectRestrictNodes.)
423: * Another complication is that we want to be able to only only push
424: * projections and restrictions down to this ProjectRestrict, but
425: * we want to be able to push them through as well.
426: * So, we:
427: * o call subquery.preprocess() which returns a tree with
428: * a SelectNode or a RowResultSetNode on top.
429: * o If the FSqry is flattenable(), then we return (so that the
430: * caller can then call flatten()), otherwise we:
431: * o generate a PRN, whose RCL is the FSqry's RCL, on top of the result.
432: * o create a referencedTableMap for the PRN which represents
433: * the FSqry's tableNumber, since ColumnReferences in the outer
434: * query block would be referring to that one.
435: * (This will allow us to push restrictions down to the PRN.)
436: */
437:
438: subquery = subquery.preprocess(numTables, gbl, fromList);
439:
440: /* Return if the FSqry is flattenable()
441: * NOTE: We can't flatten a FromSubquery if there is a group by list
442: * because the group by list must be ColumnReferences. For:
443: * select c1 from v1 group by c1,
444: * where v1 is select 1 from t1
445: * The expression under the last redundant ResultColumn is an IntConstantNode,
446: * not a ColumnReference.
447: * We also do not flatten a subquery if tableProperties is non-null,
448: * as the user is specifying 1 or more properties for the derived table,
449: * which could potentially be lost on the flattening.
450: * RESOLVE - this is too restrictive.
451: */
452: if ((gbl == null || gbl.size() == 0) && tableProperties == null
453: && subquery.flattenableInFromSubquery(fromList)) {
454: /* Set our table map to the subquery's table map. */
455: setReferencedTableMap(subquery.getReferencedTableMap());
456: return this ;
457: }
458:
459: return extractSubquery(numTables);
460: }
461:
462: /**
463: * Extract out and return the subquery, with a PRN on top.
464: * (See FromSubquery.preprocess() for more details.)
465: *
466: * @param numTables The number of tables in the DML Statement
467: *
468: * @return ResultSetNode at top of extracted tree.
469: *
470: * @exception StandardException Thrown on error
471: */
472:
473: public ResultSetNode extractSubquery(int numTables)
474: throws StandardException {
475: JBitSet newJBS;
476: ResultSetNode newPRN;
477:
478: newPRN = (ResultSetNode) getNodeFactory().getNode(
479: C_NodeTypes.PROJECT_RESTRICT_NODE, subquery, /* Child ResultSet */
480: resultColumns, /* Projection */
481: null, /* Restriction */
482: null, /* Restriction as PredicateList */
483: null, /* Subquerys in Projection */
484: null, /* Subquerys in Restriction */
485: tableProperties, getContextManager());
486:
487: /* Set up the PRN's referencedTableMap */
488: newJBS = new JBitSet(numTables);
489: newJBS.set(tableNumber);
490: newPRN.setReferencedTableMap(newJBS);
491: ((FromTable) newPRN).setTableNumber(tableNumber);
492:
493: return newPRN;
494: }
495:
496: /**
497: * Flatten this FSqry into the outer query block. The steps in
498: * flattening are:
499: * o Mark all ResultColumns as redundant, so that they are "skipped over"
500: * at generate().
501: * o Append the wherePredicates to the outer list.
502: * o Return the fromList so that the caller will merge the 2 lists
503: * RESOLVE - FSqrys with subqueries are currently not flattenable. Some of
504: * them can be flattened, however. We need to merge the subquery list when
505: * we relax this restriction.
506: *
507: * NOTE: This method returns NULL when flattening RowResultSetNodes
508: * (the node for a VALUES clause). The reason is that no reference
509: * is left to the RowResultSetNode after flattening is done - the
510: * expressions point directly to the ValueNodes in the RowResultSetNode's
511: * ResultColumnList.
512: *
513: * @param rcl The RCL from the outer query
514: * @param outerPList PredicateList to append wherePredicates to.
515: * @param sql The SubqueryList from the outer query
516: * @param gbl The group by list, if any
517: *
518: * @return FromList The fromList from the underlying SelectNode.
519: *
520: * @exception StandardException Thrown on error
521: */
522: public FromList flatten(ResultColumnList rcl,
523: PredicateList outerPList, SubqueryList sql, GroupByList gbl)
524:
525: throws StandardException {
526: FromList fromList = null;
527: SelectNode selectNode;
528:
529: resultColumns.setRedundant();
530:
531: subquery.getResultColumns().setRedundant();
532:
533: /*
534: ** RESOLVE: Each type of result set should know how to remap itself.
535: */
536: if (subquery instanceof SelectNode) {
537: selectNode = (SelectNode) subquery;
538: fromList = selectNode.getFromList();
539:
540: // selectNode.getResultColumns().setRedundant();
541:
542: if (selectNode.getWherePredicates().size() > 0) {
543: outerPList.destructiveAppend(selectNode
544: .getWherePredicates());
545: }
546:
547: if (selectNode.getWhereSubquerys().size() > 0) {
548: sql.destructiveAppend(selectNode.getWhereSubquerys());
549: }
550: } else if (!(subquery instanceof RowResultSetNode)) {
551: if (SanityManager.DEBUG) {
552: SanityManager
553: .THROWASSERT("subquery expected to be either a SelectNode or a RowResultSetNode, but is a "
554: + subquery.getClass().getName());
555: }
556: }
557:
558: /* Remap all ColumnReferences from the outer query to this node.
559: * (We replace those ColumnReferences with clones of the matching
560: * expression in the SELECT's RCL.
561: */
562: rcl.remapColumnReferencesToExpressions();
563: outerPList.remapColumnReferencesToExpressions();
564: if (gbl != null) {
565: gbl.remapColumnReferencesToExpressions();
566: }
567:
568: return fromList;
569: }
570:
571: /**
572: * Get the exposed name for this table, which is the name that can
573: * be used to refer to it in the rest of the query.
574: *
575: * @return The exposed name for this table.
576: */
577:
578: public String getExposedName() {
579: return correlationName;
580: }
581:
582: /**
583: * Expand a "*" into a ResultColumnList with all of the
584: * result columns from the subquery.
585: * @exception StandardException Thrown on error
586: */
587: public ResultColumnList getAllResultColumns(TableName allTableName)
588: throws StandardException {
589: ResultColumnList rcList = null;
590: TableName exposedName;
591: TableName toCompare;
592:
593: if (allTableName != null)
594: toCompare = makeTableName(allTableName.getSchemaName(),
595: correlationName);
596: else
597: toCompare = makeTableName(null, correlationName);
598:
599: if (allTableName != null && !allTableName.equals(toCompare)) {
600: return null;
601: }
602:
603: /* Cache exposed name for this table.
604: * The exposed name becomes the qualifier for each column
605: * in the expanded list.
606: */
607: exposedName = makeTableName(null, correlationName);
608:
609: rcList = (ResultColumnList) getNodeFactory().getNode(
610: C_NodeTypes.RESULT_COLUMN_LIST, getContextManager());
611:
612: /* Build a new result column list based off of resultColumns.
613: * NOTE: This method will capture any column renaming due to
614: * a derived column list.
615: */
616: int rclSize = resultColumns.size();
617: for (int index = 0; index < rclSize; index++) {
618: ResultColumn resultColumn = (ResultColumn) resultColumns
619: .elementAt(index);
620: ValueNode valueNode;
621: String columnName;
622:
623: if (resultColumn.isGenerated()) {
624: continue;
625: }
626:
627: // Build a ResultColumn/ColumnReference pair for the column //
628: columnName = resultColumn.getName();
629: boolean isNameGenerated = resultColumn.isNameGenerated();
630:
631: /* If this node was generated for a GROUP BY, then tablename for the CR, if any,
632: * comes from the source RC.
633: */
634: TableName tableName;
635:
636: if (correlationName == null && generatedForGroupByClause) {
637: tableName = makeTableName(null, resultColumn
638: .getTableName());
639: } else {
640: tableName = exposedName;
641: }
642: valueNode = (ValueNode) getNodeFactory().getNode(
643: C_NodeTypes.COLUMN_REFERENCE, columnName,
644: tableName, getContextManager());
645: resultColumn = (ResultColumn) getNodeFactory().getNode(
646: C_NodeTypes.RESULT_COLUMN, columnName, valueNode,
647: getContextManager());
648:
649: resultColumn.setNameGenerated(isNameGenerated);
650: // Build the ResultColumnList to return //
651: rcList.addResultColumn(resultColumn);
652: }
653: return rcList;
654: }
655:
656: /**
657: * @see QueryTreeNode#disablePrivilegeCollection
658: */
659: public void disablePrivilegeCollection() {
660: super .disablePrivilegeCollection();
661: subquery.disablePrivilegeCollection();
662: }
663:
664: /**
665: * Search to see if a query references the specifed table name.
666: *
667: * @param name Table name (String) to search for.
668: * @param baseTable Whether or not name is for a base table
669: *
670: * @return true if found, else false
671: *
672: * @exception StandardException Thrown on error
673: */
674: public boolean referencesTarget(String name, boolean baseTable)
675: throws StandardException {
676: return subquery.referencesTarget(name, baseTable);
677: }
678:
679: /**
680: * Return true if the node references SESSION schema tables (temporary or permanent)
681: *
682: * @return true if references SESSION schema tables, else false
683: *
684: * @exception StandardException Thrown on error
685: */
686: public boolean referencesSessionSchema() throws StandardException {
687: return subquery.referencesSessionSchema();
688: }
689:
690: /**
691: * Bind any untyped null nodes to the types in the given ResultColumnList.
692: *
693: * @param bindingRCL The ResultColumnList with the types to bind to.
694: *
695: * @exception StandardException Thrown on error
696: */
697: public void bindUntypedNullsToResultColumns(
698: ResultColumnList bindingRCL) throws StandardException {
699: subquery.bindUntypedNullsToResultColumns(bindingRCL);
700: }
701:
702: /**
703: * Decrement (query block) level (0-based) for this FromTable.
704: * This is useful when flattening a subquery.
705: *
706: * @param decrement The amount to decrement by.
707: */
708: void decrementLevel(int decrement) {
709: super.decrementLevel(decrement);
710: subquery.decrementLevel(decrement);
711: }
712: }
|