001: /*
002:
003: Derby - Class org.apache.derby.impl.sql.compile.CurrentOfNode
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.services.context.ContextManager;
025:
026: import org.apache.derby.iapi.sql.compile.CompilerContext;
027: import org.apache.derby.iapi.sql.compile.OptimizablePredicateList;
028: import org.apache.derby.iapi.sql.compile.Optimizer;
029: import org.apache.derby.iapi.sql.compile.CostEstimate;
030: import org.apache.derby.iapi.sql.compile.OptimizableList;
031: import org.apache.derby.iapi.sql.compile.Optimizable;
032: import org.apache.derby.iapi.sql.compile.RequiredRowOrdering;
033: import org.apache.derby.iapi.sql.compile.RowOrdering;
034: import org.apache.derby.iapi.sql.compile.C_NodeTypes;
035:
036: import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
037:
038: import org.apache.derby.iapi.sql.dictionary.DataDictionary;
039: import org.apache.derby.iapi.sql.dictionary.ColumnDescriptor;
040: import org.apache.derby.iapi.sql.dictionary.ColumnDescriptorList;
041: import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor;
042: import org.apache.derby.iapi.sql.dictionary.SchemaDescriptor;
043: import org.apache.derby.iapi.sql.dictionary.TableDescriptor;
044:
045: import org.apache.derby.iapi.types.TypeId;
046:
047: import org.apache.derby.iapi.sql.execute.ExecCursorTableReference;
048: import org.apache.derby.iapi.sql.execute.ExecPreparedStatement;
049:
050: import org.apache.derby.iapi.types.DataValueDescriptor;
051: import org.apache.derby.iapi.sql.ResultSet;
052: import org.apache.derby.iapi.sql.Activation;
053:
054: import org.apache.derby.iapi.reference.SQLState;
055:
056: import org.apache.derby.iapi.sql.execute.CursorResultSet;
057:
058: import org.apache.derby.iapi.types.RowLocation;
059:
060: import org.apache.derby.iapi.store.access.TransactionController;
061: import org.apache.derby.iapi.reference.ClassName;
062:
063: import org.apache.derby.iapi.error.StandardException;
064:
065: import org.apache.derby.iapi.services.compiler.MethodBuilder;
066:
067: import org.apache.derby.iapi.services.sanity.SanityManager;
068:
069: import org.apache.derby.impl.sql.compile.ActivationClassBuilder;
070:
071: import org.apache.derby.iapi.util.JBitSet;
072: import org.apache.derby.iapi.services.classfile.VMOpcode;
073:
074: import java.util.Properties;
075:
076: /**
077: * The CurrentOf operator is used by positioned DELETE
078: * and UPDATE to get the current row and location
079: * for the target cursor. The bind() operations for
080: * positioned DELETE and UPDATE add a column to
081: * the select list under the statement for the row location
082: * accessible from this node.
083: *
084: * This node is placed in the from clause of the select
085: * generated for the delete or update operation. It acts
086: * much like a FromBaseTable, using the information about
087: * the target table of the cursor to provide information.
088: *
089: * @author ames
090: */
091: public final class CurrentOfNode extends FromTable {
092:
093: private String cursorName;
094: private ExecPreparedStatement preStmt;
095: private TableName exposedTableName;
096: private TableName baseTableName;
097: private CostEstimate singleScanCostEstimate;
098:
099: //
100: // initializers
101: //
102: public void init(Object correlationName, Object cursor,
103: Object tableProperties) {
104: super .init(correlationName, tableProperties);
105: cursorName = (String) cursor;
106: }
107:
108: /*
109: * Optimizable interface
110: */
111:
112: /**
113: * @see Optimizable#estimateCost
114: *
115: * @exception StandardException Thrown on error
116: */
117: public CostEstimate estimateCost(OptimizablePredicateList predList,
118: ConglomerateDescriptor cd, CostEstimate outerCost,
119: Optimizer optimizer, RowOrdering rowOrdering)
120: throws StandardException {
121: /*
122: ** Get the cost of a single scan of this result set.
123: **
124: ** Assume for now that the cost of a CURRENT OF is zero, with one row
125: ** fetched. Is this true, and if not, does it make a difference?
126: ** CURRENT OF can only occur when there is only one table in the
127: ** FROM list, and when the only "predicate" is the WHERE CURRENT OF,
128: ** so there's nothing to optimize in this case.
129: */
130: if (singleScanCostEstimate == null) {
131: singleScanCostEstimate = optimizer.newCostEstimate();
132: }
133:
134: singleScanCostEstimate.setCost(0.0d, 1.0d, 1.0d);
135: getBestAccessPath().setCostEstimate(singleScanCostEstimate);
136: getBestSortAvoidancePath().setCostEstimate(
137: singleScanCostEstimate);
138:
139: return singleScanCostEstimate;
140: }
141:
142: //
143: // FromTable interface
144: //
145:
146: /**
147: * Binding this FromTable means finding the prepared statement
148: * for the cursor and creating the result columns (the columns
149: * updatable on that cursor).
150: *
151: * We expect someone else to verify that the target table
152: * of the positioned update or delete is the table under this cursor.
153: *
154: * @param dataDictionary The DataDictionary to use for binding
155: * @param fromListParam FromList to use/append to.
156: *
157: * @return ResultSetNode Returns this.
158: *
159: * @exception StandardException Thrown on error
160: */
161: public ResultSetNode bindNonVTITables(
162: DataDictionary dataDictionary, FromList fromListParam)
163: throws StandardException {
164:
165: // verify that the cursor exists
166: // and create a dependency on it
167:
168: preStmt = getCursorStatement();
169: if ((preStmt != null) && (!preStmt.upToDate())) {
170: preStmt.makeValid(getLanguageConnectionContext()); // need to have the query tree
171: if (!preStmt.isValid()) // can't make it valid, say not found
172: preStmt = null;
173: }
174:
175: if (preStmt == null) {
176: throw StandardException.newException(
177: SQLState.LANG_CURSOR_NOT_FOUND, cursorName);
178: }
179:
180: // verify that the cursor is updatable (UPDATE is responsible
181: // for checking that the right columns are updatable)
182: if (preStmt.getUpdateMode() != CursorNode.UPDATE) {
183: String printableString = (cursorName == null) ? ""
184: : cursorName;
185: throw StandardException
186: .newException(SQLState.LANG_CURSOR_NOT_UPDATABLE,
187: printableString);
188: }
189:
190: getCompilerContext().createDependency(preStmt);
191:
192: ExecCursorTableReference refTab = preStmt.getTargetTable();
193: String schemaName = refTab.getSchemaName();
194: exposedTableName = makeTableName(null, refTab.getExposedName());
195: baseTableName = makeTableName(schemaName, refTab.getBaseName());
196: SchemaDescriptor tableSchema = null;
197: tableSchema = getSchemaDescriptor(refTab.getSchemaName());
198:
199: /*
200: ** This will only happen when we are binding against a publication
201: ** dictionary w/o the schema we are interested in.
202: */
203: if (tableSchema == null) {
204: throw StandardException.newException(
205: SQLState.LANG_SCHEMA_DOES_NOT_EXIST, refTab
206: .getSchemaName());
207: }
208:
209: /* Create dependency on target table, in case table not named in
210: * positioned update/delete. Make sure we find the table descriptor,
211: * we may fail to find it if we are binding a publication.
212: */
213: TableDescriptor td = getTableDescriptor(refTab.getBaseName(),
214: tableSchema);
215:
216: if (td == null) {
217: throw StandardException
218: .newException(SQLState.LANG_TABLE_NOT_FOUND, refTab
219: .getBaseName());
220: }
221:
222: /*
223: ** Add all the result columns from the target table.
224: ** For now, all updatable cursors have all columns
225: ** from the target table. In the future, we should
226: ** relax this so that the cursor may do a partial
227: ** read and then the current of should make sure that
228: ** it can go to the base table to get all of the
229: ** columns needed by the referencing positioned
230: ** DML. In the future, we'll probably need to get
231: ** the result columns from preparedStatement and
232: ** turn them into an RCL that we can run with.
233: */
234: resultColumns = (ResultColumnList) getNodeFactory().getNode(
235: C_NodeTypes.RESULT_COLUMN_LIST, getContextManager());
236: ColumnDescriptorList cdl = td.getColumnDescriptorList();
237: int cdlSize = cdl.size();
238:
239: for (int index = 0; index < cdlSize; index++) {
240: /* Build a ResultColumn/BaseColumnNode pair for the column */
241: ColumnDescriptor colDesc = (ColumnDescriptor) cdl
242: .elementAt(index);
243:
244: BaseColumnNode bcn = (BaseColumnNode) getNodeFactory()
245: .getNode(C_NodeTypes.BASE_COLUMN_NODE,
246: colDesc.getColumnName(), exposedTableName,
247: colDesc.getType(), getContextManager());
248: ResultColumn rc = (ResultColumn) getNodeFactory().getNode(
249: C_NodeTypes.RESULT_COLUMN, colDesc, bcn,
250: getContextManager());
251:
252: /* Build the ResultColumnList to return */
253: resultColumns.addResultColumn(rc);
254: }
255:
256: /* Assign the tableNumber */
257: if (tableNumber == -1) // allow re-bind, in which case use old number
258: tableNumber = getCompilerContext().getNextTableNumber();
259:
260: return this ;
261: }
262:
263: /**
264: * Bind the expressions in this ResultSetNode. This means binding the
265: * sub-expressions, as well as figuring out what the return type is for
266: * each expression.
267: *
268: * @param fromListParam FromList to use/append to.
269: */
270: public void bindExpressions(FromList fromListParam) {
271: /* No expressions to bind for a CurrentOfNode.
272: * NOTE - too involved to optimize so that this method
273: * doesn't get called, so just do nothing.
274: */
275: }
276:
277: /**
278: * Try to find a ResultColumn in the table represented by this CurrentOfNode
279: * that matches the name in the given ColumnReference.
280: *
281: * @param columnReference The columnReference whose name we're looking
282: * for in the given table.
283: *
284: * @return A ResultColumn whose expression is the ColumnNode
285: * that matches the ColumnReference.
286: * Returns null if there is no match.
287: *
288: * @exception StandardException Thrown on error
289: */
290:
291: public ResultColumn getMatchingColumn(
292: ColumnReference columnReference) throws StandardException {
293:
294: ResultColumn resultColumn = null;
295: TableName columnsTableName;
296:
297: columnsTableName = columnReference.getTableNameNode();
298:
299: if (columnsTableName != null)
300: if (columnsTableName.getSchemaName() == null
301: && correlationName == null)
302: columnsTableName.bind(this .getDataDictionary());
303:
304: if (SanityManager.DEBUG) {
305: SanityManager.ASSERT(preStmt != null,
306: "must have prepared statement");
307: }
308:
309: /*
310: * We use the base table name of the target table.
311: * This is necessary since we will be comparing with the table in
312: * the delete or update statement which doesn't have a correlation
313: * name. The select for which this column is created might have a
314: * correlation name and so we won't find it if we look for exposed names
315: * We shouldn't have to worry about multiple table since there should be
316: * only one table. Beetle 4419
317: */
318: if (SanityManager.DEBUG) {
319: SanityManager.ASSERT(baseTableName != null,
320: "no name on target table");
321: }
322:
323: if (baseTableName != null)
324: if (baseTableName.getSchemaName() == null
325: && correlationName == null)
326: baseTableName.bind(this .getDataDictionary());
327:
328: /*
329: * If the column did not specify a name, or the specified name
330: * matches the table we're looking at, see whether the column
331: * is in this table, and also whether it is in the for update list.
332: */
333: if ((columnsTableName == null)
334: || (columnsTableName.getFullTableName()
335: .equals(baseTableName.getFullTableName()))
336: || ((correlationName != null) && correlationName
337: .equals(columnsTableName.getTableName()))) {
338: boolean notfound = false;
339:
340: resultColumn = resultColumns
341: .getResultColumn(columnReference.getColumnName());
342:
343: if (resultColumn != null) {
344: // If we found the ResultColumn, set the ColumnReference's
345: // table number accordingly. Note: we used to only set
346: // the tableNumber for correlated references (as part of
347: // changes for DERBY-171) but inspection of code (esp.
348: // the comments in FromList.bindColumnReferences() and
349: // the getMatchingColumn() methods on other FromTables)
350: // suggests that we should always set the table number
351: // if we've found the ResultColumn. So we do that here.
352: columnReference.setTableNumber(tableNumber);
353:
354: // If there is a result column, are we really updating it?
355: // If so, verify that the column is updatable as well
356: notfound = (resultColumn.updatableByCursor() && !foundString(
357: preStmt.getUpdateColumns(), columnReference
358: .getColumnName()));
359: } else {
360: notfound = true;
361: }
362:
363: if (notfound) {
364: String printableString = (cursorName == null) ? ""
365: : cursorName;
366: throw StandardException.newException(
367: SQLState.LANG_COLUMN_NOT_UPDATABLE_IN_CURSOR,
368: columnReference.getColumnName(),
369: printableString);
370: }
371: }
372:
373: return resultColumn;
374: }
375:
376: /**
377: * Preprocess a CurrentOfNode. For a CurrentOfNode, this simply means allocating
378: * a referenced table map to avoid downstream NullPointerExceptions.
379: * NOTE: There are no bits set in the referenced table map.
380: *
381: * @param numTables The number of tables in the DML Statement
382: * @param gbl The group by list, if any
383: * @param fromList The from list, if any
384: *
385: * @return ResultSetNode at top of preprocessed tree.
386: *
387: * @exception StandardException Thrown on error
388: */
389:
390: public ResultSetNode preprocess(int numTables, GroupByList gbl,
391: FromList fromList) throws StandardException {
392: /* Generate an empty referenced table map */
393: referencedTableMap = new JBitSet(numTables);
394: return this ;
395: }
396:
397: /**
398: * Optimize this CurrentOfNode. Nothing to do.
399: *
400: * @param dataDictionary The DataDictionary to use for optimization
401: * @param predicateList The PredicateList to optimize. This should
402: * be a single-table predicate with the table
403: * the same as the table in this FromTable.
404: * @param outerRows The number of outer joining rows
405: *
406: * @return ResultSetNode The top of the optimized subtree.
407: *
408: * @exception StandardException Thrown on error
409: */
410: public ResultSetNode optimize(DataDictionary dataDictionary,
411: PredicateList predicateList, double outerRows)
412: throws StandardException {
413: /* Get an optimizer so we can get a cost */
414: Optimizer optimizer = getOptimizer((FromList) getNodeFactory()
415: .getNode(C_NodeTypes.FROM_LIST,
416: getNodeFactory().doJoinOrderOptimization(),
417: this , getContextManager()), predicateList,
418: dataDictionary, (RequiredRowOrdering) null);
419:
420: /* Assume there is no cost associated with fetching the current row */
421: bestCostEstimate = optimizer.newCostEstimate();
422: bestCostEstimate.setCost(0.0d, outerRows, outerRows);
423:
424: return this ;
425: }
426:
427: /**
428: * Generation on a CurrentOfNode creates a scan on the
429: * cursor, CurrentOfResultSet.
430: * <p>
431: * This routine will generate and return a call of the form:
432: * <pre><verbatim>
433: ResultSetFactory.getCurrentOfResultSet(cursorName)
434: </verbatim></pre>
435: *
436: * @param acb The ActivationClassBuilder for the class being built
437: * @param mb The execute() method to be built
438: *
439: * @exception StandardException Thrown on error
440: */
441: public void generate(ActivationClassBuilder acb, MethodBuilder mb)
442: throws StandardException {
443:
444: if (SanityManager.DEBUG)
445: SanityManager.ASSERT(!statementResultSet,
446: "CurrentOfNode not expected to be statement node");
447:
448: /* Get the next ResultSet #, so that we can number this ResultSetNode, its
449: * ResultColumnList and ResultSet.
450: */
451: assignResultSetNumber();
452:
453: mb.pushThis(); // for the putField
454:
455: // The generated java returned by this method is the expression:
456: // ResultSetFactory.getCurrentOfResultSet(
457: // #cursorName(), this, resultSetNumber)
458:
459: acb.pushGetResultSetFactoryExpression(mb);
460:
461: mb.push(cursorName);
462: acb.pushThisAsActivation(mb);
463: mb.push(resultSetNumber);
464: mb.push(preStmt.getObjectName());
465:
466: mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null,
467: "getCurrentOfResultSet", ClassName.NoPutResultSet, 4);
468:
469: mb.cast(ClassName.CursorResultSet);
470:
471: // the current of scan generator is what we return
472: /* This table is the target of an update or a delete, so we must
473: * wrap the Expression up in an assignment expression before
474: * returning. Delete or update use the field that is set
475: * to calculate the CurrentRowLocation value.
476: * NOTE - scanExpress is a ResultSet. We will need to cast it to the
477: * appropriate subclass.
478: * For example, for a DELETE, instead of returning a call to the
479: * ResultSetFactory, we will generate and return:
480: * this.SCANRESULTSET = (cast to appropriate ResultSet type)
481: * The outer cast back to ResultSet is needed so that
482: * we invoke the appropriate method in the call to the ResultSetFactory
483: */
484:
485: mb.putField((String) null, acb
486: .getRowLocationScanResultSetName(),
487: ClassName.CursorResultSet);
488: mb.cast(ClassName.NoPutResultSet);
489:
490: // add a check at activation reset time to see if the cursor has
491: // changed underneath us. Doing it in the constructor allows the
492: // compilation to happen
493: MethodBuilder rmb = acb.startResetMethod();
494:
495: rmb.pushThis();
496: rmb.push(cursorName);
497: rmb.push(preStmt.getObjectName());
498: rmb.callMethod(VMOpcode.INVOKEVIRTUAL,
499: ClassName.BaseActivation, "checkPositionedStatement",
500: "void", 2);
501:
502: rmb.methodReturn();
503: rmb.complete();
504: }
505:
506: /**
507: * Prints the sub-nodes of this object. See QueryTreeNode.java for
508: * how tree printing is supposed to work.
509: *
510: * @param depth The depth of this node in the tree
511: */
512: public void printSubNodes(int depth) {
513: if (SanityManager.DEBUG) {
514: super .printSubNodes(depth);
515:
516: printLabel(depth, "cursor: ");
517: }
518: }
519:
520: /**
521: * Convert this object to a String. See comments in QueryTreeNode.java
522: * for how this should be done for tree printing.
523: *
524: * @return This object as a String
525: */
526: public String toString() {
527: if (SanityManager.DEBUG) {
528: return "preparedStatement: "
529: + (preStmt == null ? "no prepared statement yet\n"
530: : preStmt.toString() + "\n") + cursorName
531: + "\n" + super .toString();
532: } else {
533: return "";
534: }
535: }
536:
537: //
538: // class interface
539: //
540:
541: public String getExposedName() {
542: return exposedTableName.getFullTableName();
543: }
544:
545: public TableName getExposedTableName() {
546: return exposedTableName;
547: }
548:
549: public TableName getBaseCursorTargetTableName() {
550: return baseTableName;
551: }
552:
553: public String getCursorName() {
554: return cursorName;
555: }
556:
557: /**
558: * Return the CursorNode associated with a positioned update/delete.
559: *
560: * @return CursorNode The associated CursorNode.
561: *
562: */
563: ExecPreparedStatement getCursorStatement() {
564: Activation activation = getLanguageConnectionContext()
565: .lookupCursorActivation(cursorName);
566:
567: if (activation == null)
568: return null;
569:
570: return activation.getPreparedStatement();
571: }
572:
573: /**
574: * Get the lock mode for this table as the target of an update statement
575: * (a delete or update). This is implemented only for base tables and
576: * CurrentOfNodes.
577: *
578: * @see TransactionController
579: *
580: * @return The lock mode
581: */
582: public int updateTargetLockMode() {
583: /* Do row locking for positioned update/delete */
584: return TransactionController.MODE_RECORD;
585: }
586: }
|