001: /*
002:
003: Derby - Class org.apache.derby.impl.sql.compile.CursorNode
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.C_NodeTypes;
027:
028: import org.apache.derby.iapi.sql.conn.Authorizer;
029: import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
030:
031: import org.apache.derby.iapi.error.StandardException;
032: import org.apache.derby.iapi.reference.SQLState;
033:
034: import org.apache.derby.iapi.sql.ResultColumnDescriptor;
035:
036: import org.apache.derby.iapi.sql.dictionary.DataDictionary;
037: import org.apache.derby.iapi.sql.dictionary.DataDictionaryContext;
038: import org.apache.derby.iapi.sql.dictionary.SchemaDescriptor;
039: import org.apache.derby.iapi.sql.dictionary.TableDescriptor;
040:
041: import org.apache.derby.iapi.services.sanity.SanityManager;
042:
043: import org.apache.derby.iapi.services.compiler.MethodBuilder;
044:
045: import org.apache.derby.impl.sql.compile.ActivationClassBuilder;
046:
047: import org.apache.derby.impl.sql.CursorInfo;
048: import org.apache.derby.impl.sql.CursorTableReference;
049:
050: import java.util.ArrayList;
051: import java.util.Vector;
052:
053: /**
054: * A CursorNode represents a result set that can be returned to a client.
055: * A cursor can be a named cursor created by the DECLARE CURSOR statement,
056: * or it can be an unnamed cursor associated with a SELECT statement (more
057: * precisely, a table expression that returns rows to the client). In the
058: * latter case, the cursor does not have a name.
059: *
060: * @author Jeff Lichtman
061: */
062:
063: public class CursorNode extends DMLStatementNode {
064: public final static int UNSPECIFIED = 0;
065: public final static int READ_ONLY = 1;
066: public final static int UPDATE = 2;
067:
068: private String name;
069: private OrderByList orderByList;
070: private String statementType;
071: private int updateMode;
072: private boolean needTarget;
073:
074: /**
075: ** There can only be a list of updatable columns when FOR UPDATE
076: ** is specified as part of the cursor specification.
077: */
078: private Vector updatableColumns;
079: private FromTable updateTable;
080: private ResultColumnDescriptor[] targetColumnDescriptors;
081:
082: //If cursor references session schema tables, save the list of those table names in savedObjects in compiler context
083: //Following is the position of the session table names list in savedObjects in compiler context
084: //At generate time, we save this position in activation for easy access to session table names list from compiler context
085: private int indexOfSessionTableNamesInSavedObjects = -1;
086:
087: /**
088: * Initializer for a CursorNode
089: *
090: * @param statementType Type of statement (SELECT, UPDATE, INSERT)
091: * @param resultSet A ResultSetNode specifying the result set for
092: * the cursor
093: * @param name The name of the cursor, null if no name
094: * @param orderByList The order by list for the cursor, null if no
095: * order by list
096: * @param updateMode The user-specified update mode for the cursor,
097: * for example, CursorNode.READ_ONLY
098: * @param updatableColumns The list of updatable columns specified by
099: * the user in the FOR UPDATE clause, null if no
100: * updatable columns specified. May only be
101: * provided if the updateMode parameter is
102: * CursorNode.UPDATE.
103: */
104:
105: public void init(Object statementType, Object resultSet,
106: Object name, Object orderByList, Object updateMode,
107: Object updatableColumns) {
108: init(resultSet);
109: this .name = (String) name;
110: this .statementType = (String) statementType;
111: this .orderByList = (OrderByList) orderByList;
112:
113: this .updateMode = ((Integer) updateMode).intValue();
114: this .updatableColumns = (Vector) updatableColumns;
115:
116: /*
117: ** This is a sanity check and not an error since the parser
118: ** controls setting updatableColumns and updateMode.
119: */
120: if (SanityManager.DEBUG)
121: SanityManager
122: .ASSERT(this .updatableColumns == null
123: || this .updatableColumns.size() == 0
124: || this .updateMode == UPDATE,
125: "Can only have explicit updatable columns if update mode is UPDATE");
126: }
127:
128: /**
129: * Convert this object to a String. See comments in QueryTreeNode.java
130: * for how this should be done for tree printing.
131: *
132: * @return This object as a String
133: */
134:
135: public String toString() {
136: if (SanityManager.DEBUG) {
137: return "name: " + name + "\n" + "updateMode: "
138: + updateModeString(updateMode) + "\n"
139: + super .toString();
140: } else {
141: return "";
142: }
143: }
144:
145: public String statementToString() {
146: return statementType;
147: }
148:
149: /**
150: * Support routine for translating an updateMode identifier to a String
151: *
152: * @param updateMode An updateMode identifier
153: *
154: * @return A String representing the update mode.
155: */
156:
157: private static String updateModeString(int updateMode) {
158: if (SanityManager.DEBUG) {
159: switch (updateMode) {
160: case UNSPECIFIED:
161: return "UNSPECIFIED (" + UNSPECIFIED + ")";
162:
163: case READ_ONLY:
164: return "READ_ONLY (" + READ_ONLY + ")";
165:
166: case UPDATE:
167: return "UPDATE (" + UPDATE + ")";
168:
169: default:
170: return "UNKNOWN VALUE (" + updateMode + ")";
171: }
172: } else {
173: return "";
174: }
175: }
176:
177: /**
178: * Prints the sub-nodes of this object. See QueryTreeNode.java for
179: * how tree printing is supposed to work.
180: *
181: * @param depth The depth of this node in the tree
182: */
183:
184: public void printSubNodes(int depth) {
185: if (SanityManager.DEBUG) {
186: super .printSubNodes(depth);
187:
188: printLabel(depth, "orderByList: ");
189: if (orderByList != null)
190: orderByList.treePrint(depth + 1);
191: }
192: }
193:
194: /**
195: * Bind this CursorNode. This means looking up tables and columns and
196: * getting their types, and figuring out the result types of all
197: * expressions, as well as doing view resolution, permissions checking,
198: * etc. It also includes determining whether an UNSPECIFIED cursor
199: * is updatable or not, and verifying that an UPDATE cursor actually is.
200: *
201: * @return The bound query tree
202: *
203: * @exception StandardException Thrown on error
204: */
205:
206: public QueryTreeNode bind() throws StandardException {
207: DataDictionary dataDictionary;
208:
209: dataDictionary = getDataDictionary();
210:
211: // This is how we handle queries like: SELECT A FROM T ORDER BY B.
212: // We pull up the order by columns (if they don't appear in the SELECT
213: // LIST) and let the bind() do the job. Note that the pullup is done
214: // before the bind() and we may avoid pulling up ORDERBY columns that
215: // would otherwise be avoided, e.g., "SELECT * FROM T ORDER BY B".
216: // Pulled-up ORDERBY columns that are duplicates (like the above "SELECT
217: // *" query will be removed in bindOrderByColumns().
218: // Finally, given that extra columns may be added to the SELECT list, we
219: // inject a ProjectRestrictNode so that only the user-specified columns
220: // will be returned (see genProjectRestrict() in SelectNode.java).
221: if (orderByList != null) {
222: orderByList.pullUpOrderByColumns(resultSet);
223: }
224:
225: getCompilerContext().pushCurrentPrivType(getPrivType());
226: try {
227: FromList fromList = (FromList) getNodeFactory().getNode(
228: C_NodeTypes.FROM_LIST,
229: getNodeFactory().doJoinOrderOptimization(),
230: getContextManager());
231:
232: /* Check for ? parameters directly under the ResultColums */
233: resultSet.rejectParameters();
234:
235: super .bind(dataDictionary);
236:
237: // bind the query expression
238: resultSet.bindResultColumns(fromList);
239:
240: // this rejects any untyped nulls in the select list
241: // pass in null to indicate that we don't have any
242: // types for this node
243: resultSet.bindUntypedNullsToResultColumns(null);
244:
245: // Reject any XML values in the select list; JDBC doesn't
246: // define how we bind these out, so we don't allow it.
247: resultSet.rejectXMLValues();
248:
249: /* Verify that all underlying ResultSets reclaimed their FromList */
250: if (SanityManager.DEBUG) {
251: SanityManager
252: .ASSERT(
253: fromList.size() == 0,
254: "fromList.size() is expected to be 0, not "
255: + fromList.size()
256: + " on return from RS.bindExpressions()");
257: }
258: } finally {
259: getCompilerContext().popCurrentPrivType();
260: }
261:
262: // bind the order by
263: if (orderByList != null) {
264: orderByList.bindOrderByColumns(resultSet);
265: }
266:
267: // bind the updatability
268:
269: // if it says it is updatable, verify it.
270: if (updateMode == UPDATE) {
271: int checkedUpdateMode;
272:
273: checkedUpdateMode = determineUpdateMode(dataDictionary);
274: if (SanityManager.DEBUG)
275: SanityManager.DEBUG("DumpUpdateCheck",
276: "update mode is UPDATE (" + updateMode
277: + ") checked mode is "
278: + checkedUpdateMode);
279: if (updateMode != checkedUpdateMode)
280: throw StandardException
281: .newException(SQLState.LANG_STMT_NOT_UPDATABLE);
282: }
283:
284: // if it doesn't know if it is updatable, determine it
285: if (updateMode == UNSPECIFIED) {
286: // If the statement is opened with CONCUR_READ_ONLY, the upgrade mode is
287: // set to read only.
288:
289: // NOTE: THIS IS NOT COMPATIBLE WITH THE ISO/ANSI SQL STANDARD.
290:
291: // According to the SQL-standard:
292: // If updatability is not specified, a SELECT * FROM T will be implicitely
293: // read only in the context of a cursor which is insensitive, scrollable or
294: // have an order by clause. Otherwise it is implicitely updatable.
295:
296: // In Derby, we make a SELECT * FROM T updatable if the concurrency mode is
297: // ResultSet.CONCUR_UPDATE. If we do make all SELECT * FROM T updatable
298: // by default, we cannot use an index on any single-table select, unless it
299: // was declared FOR READ ONLY. This would be pretty terrible, so we are
300: // breaking the ANSI rules.
301:
302: if (getLanguageConnectionContext().getStatementContext()
303: .isForReadOnly()) {
304: updateMode = READ_ONLY;
305: } else {
306: updateMode = determineUpdateMode(dataDictionary);
307: }
308:
309: //if (SanityManager.DEBUG)
310: //SanityManager.DEBUG("DumpUpdateCheck","update mode is UNSPECIFIED ("+UNSPECIFIED+") checked mode is "+updateMode);
311: }
312:
313: if (updateMode == READ_ONLY) {
314: updatableColumns = null; // don't need them any more
315: }
316:
317: // bind the update columns
318: if (updateMode == UPDATE) {
319: bindUpdateColumns(updateTable);
320:
321: // If the target table is a FromBaseTable, mark the updatable
322: // columns. (I can't think of a way that an updatable table
323: // could be anything but a FromBaseTable at this point, but
324: // it's better to be careful.
325: if (updateTable instanceof FromTable) {
326: ((FromTable) updateTable)
327: .markUpdatableByCursor(updatableColumns);
328: //make sure that alongwith the FromTable, we keep other ResultSetLists
329: //in correct state too. ResultSetMetaData.isWritable looks at this to
330: //return the correct value.
331: resultSet.getResultColumns()
332: .markColumnsInSelectListUpdatableByCursor(
333: updatableColumns);
334: }
335: }
336:
337: resultSet.renameGeneratedResultNames();
338:
339: //need to look for SESSION tables only if global temporary tables declared for the connection
340: if (getLanguageConnectionContext()
341: .checkIfAnyDeclaredGlobalTempTablesForThisConnection()) {
342: //If this cursor has references to session schema tables, save the names of those tables into compiler context
343: //so they can be passed to execution phase.
344: ArrayList sessionSchemaTableNames = getSessionSchemaTableNamesForCursor();
345: if (sessionSchemaTableNames != null)
346: indexOfSessionTableNamesInSavedObjects = getCompilerContext()
347: .addSavedObject(sessionSchemaTableNames);
348: }
349:
350: return this ;
351: }
352:
353: /**
354: * Return true if the node references SESSION schema tables (temporary or permanent)
355: *
356: * @return true if references SESSION schema tables, else false
357: *
358: * @exception StandardException Thrown on error
359: */
360: public boolean referencesSessionSchema() throws StandardException {
361: //If this node references a SESSION schema table, then return true.
362: return resultSet.referencesSessionSchema();
363: }
364:
365: //Check if this cursor references any session schema tables. If so, pass those names to execution phase through savedObjects
366: //This list will be used to check if there are any holdable cursors referencing temporary tables at commit time.
367: //If yes, then the data in those temporary tables should be preserved even if they are declared with ON COMMIT DELETE ROWS option
368: protected ArrayList getSessionSchemaTableNamesForCursor()
369: throws StandardException {
370: FromList fromList = resultSet.getFromList();
371: int fromListSize = fromList.size();
372: FromTable fromTable;
373: ArrayList sessionSchemaTableNames = null;
374:
375: for (int i = 0; i < fromListSize; i++) {
376: fromTable = (FromTable) fromList.elementAt(i);
377: if (fromTable instanceof FromBaseTable
378: && isSessionSchema(fromTable.getTableDescriptor()
379: .getSchemaDescriptor())) {
380: if (sessionSchemaTableNames == null)
381: sessionSchemaTableNames = new ArrayList();
382: sessionSchemaTableNames.add(fromTable.getTableName()
383: .getTableName());
384: }
385: }
386:
387: return sessionSchemaTableNames;
388: }
389:
390: /**
391: * Take a cursor and determine if it is UPDATE
392: * or READ_ONLY based on the shape of the cursor specification.
393: * <p>
394: * The following conditions make a cursor read only:
395: * <UL>
396: * <LI>if it says FOR READ ONLY
397: * <LI>if it says ORDER BY
398: * <LI>if its query specification is not read only. At present this
399: * is explicitly tested here, with these conditions. At some future
400: * point in time, this checking ought to be moved into the
401: * ResultSet nodes themselves. The conditions for a query spec.
402: * not to be read only include:
403: * <UL>
404: * <LI>if it has a set operation such as UNION or INTERSECT, i.e.
405: * does not have a single outermost SELECT
406: * <LI>if it does not have exactly 1 table in its FROM list;
407: * 0 tables would occur if we ever support a SELECT without a
408: * FROM e.g., for generating a row without an underlying table
409: * (like what we do for an INSERT of a VALUES list); >1 tables
410: * occurs when joins are in the tree.
411: * <LI>if the table in its FROM list is not a base table (REMIND
412: * when views/from subqueries are added, this should be relaxed to
413: * be that the table is not updatable)
414: * <LI>if it has a GROUP BY or HAVING (NOTE I am assuming that if
415: * and aggregate is detected in a SELECT w/o a GROUP BY, one
416: * has been added to show that the whole table is a group)
417: * <LI> NOTE that cursors are updatable even if none of the columns
418: * in the select are updatable -- what they care about is the
419: * updatability of the columns of the target table.
420: * </UL>
421: * </UL>
422: *
423: * @return the known update mode for the cursor.
424: *
425: * @exception StandardException Thrown on error
426: */
427: private int determineUpdateMode(DataDictionary dataDictionary)
428: throws StandardException {
429: SelectNode selectNode;
430: FromList tables;
431: FromTable targetTable;
432:
433: if (updateMode == READ_ONLY) {
434: return READ_ONLY;
435: }
436:
437: if (orderByList != null) {
438: if (SanityManager.DEBUG)
439: SanityManager.DEBUG("DumpUpdateCheck",
440: "cursor has order by");
441: return READ_ONLY;
442: }
443:
444: // get the ResultSet to tell us what it thinks it is
445: // and the target table
446: if (!resultSet.isUpdatableCursor(dataDictionary)) {
447: return READ_ONLY;
448: }
449:
450: // The FOR UPDATE clause has two uses:
451: //
452: // for positioned cursor updates
453: //
454: // to change locking behaviour of the select
455: // to reduce deadlocks on subsequent updates
456: // in the same transaction.
457: //
458: // We now support this latter case, without requiring
459: // that the source of the rows be able to implement
460: // a positioned update.
461:
462: updateTable = resultSet.getCursorTargetTable();
463:
464: /* Tell the table that it is the cursor target */
465: if (updateTable.markAsCursorTargetTable()) {
466: /* Cursor is updatable - remember to generate the position code */
467: needTarget = true;
468:
469: /* We must generate the target column list at bind time
470: * because the optimizer may transform the FromBaseTable from
471: * a table scan into an index scan.
472: */
473: genTargetResultColList();
474: }
475:
476: return UPDATE;
477: }
478:
479: /**
480: * Optimize a DML statement (which is the only type of statement that
481: * should need optimizing, I think). This method over-rides the one
482: * in QueryTreeNode.
483: *
484: * This method takes a bound tree, and returns an optimized tree.
485: * It annotates the bound tree rather than creating an entirely
486: * new tree.
487: *
488: * Throws an exception if the tree is not bound, or if the binding
489: * is out of date.
490: *
491: * @return An optimized QueryTree
492: *
493: * @exception StandardException Thrown on error
494: */
495:
496: public QueryTreeNode optimize() throws StandardException {
497: // Push the order by list down to the ResultSet
498: if (orderByList != null) {
499: // If we have more than 1 ORDERBY columns, we may be able to
500: // remove duplicate columns, e.g., "ORDER BY 1, 1, 2".
501: if (orderByList.size() > 1) {
502: orderByList.removeDupColumns();
503: }
504:
505: resultSet.pushOrderByList(orderByList);
506: orderByList = null;
507: }
508: return super .optimize();
509: }
510:
511: /**
512: * Returns the type of activation this class
513: * generates.
514: *
515: * @return either (NEED_CURSOR_ACTIVATION
516: *
517: * @exception StandardException Thrown on error
518: */
519:
520: int activationKind() {
521: return NEED_CURSOR_ACTIVATION;
522: }
523:
524: /**
525: * Do code generation for this CursorNode
526: *
527: * @param acb The ActivationClassBuilder for the class being built
528: * @param mb The method the generated code is to go into
529: *
530: * @exception StandardException Thrown on error
531: */
532:
533: public void generate(ActivationClassBuilder acb, MethodBuilder mb)
534: throws StandardException {
535: if (indexOfSessionTableNamesInSavedObjects != -1) //if this cursor references session schema tables, do following
536: {
537: MethodBuilder constructor = acb.getConstructor();
538: constructor.pushThis();
539: constructor.push(indexOfSessionTableNamesInSavedObjects);
540: constructor
541: .putField(
542: org.apache.derby.iapi.reference.ClassName.BaseActivation,
543: "indexOfSessionTableNamesInSavedObjects",
544: "int");
545: constructor.endStatement();
546: }
547:
548: // generate the parameters
549: generateParameterValueSet(acb);
550:
551: // tell the outermost result set that it is the outer
552: // result set of the statement.
553: resultSet.markStatementResultSet();
554:
555: generateAuthorizeCheck(acb, mb,
556: org.apache.derby.iapi.sql.conn.Authorizer.SQL_SELECT_OP);
557:
558: // this will generate an expression that will be a ResultSet
559: resultSet.generate(acb, mb);
560:
561: /*
562: ** Generate the position code if this cursor is updatable. This
563: ** involves generating methods to get the cursor result set, and
564: ** the target result set (which is for the base row). Also,
565: ** generate code to store the cursor result set in a generated
566: ** field.
567: */
568: if (needTarget) {
569: // PUSHCOMPILE - could be put into a single method
570: acb.rememberCursor(mb);
571: acb.addCursorPositionCode();
572: }
573: }
574:
575: // class interface
576:
577: public String getUpdateBaseTableName() {
578: return (updateTable == null) ? null : updateTable
579: .getBaseTableName();
580: }
581:
582: public String getUpdateExposedTableName() throws StandardException {
583: return (updateTable == null) ? null : updateTable
584: .getExposedName();
585: }
586:
587: public String getUpdateSchemaName() throws StandardException {
588: //we need to use the base table for the schema name
589: return (updateTable == null) ? null
590: : ((FromBaseTable) updateTable).getTableNameField()
591: .getSchemaName();
592: }
593:
594: public int getUpdateMode() {
595: return updateMode;
596: }
597:
598: /**
599: * Return String[] of names from the FOR UPDATE OF List
600: *
601: * @return String[] of names from the FOR UPDATE OF list.
602: */
603: private String[] getUpdatableColumns() {
604: return (updatableColumns == null) ? (String[]) null
605: : getUpdateColumnNames();
606: }
607:
608: /**
609: Positioned update needs to know what the target result set
610: looks like. This is generated from the UpdateColumnList
611: available for the cursor, to describe the rows coming from
612: the target result set under the cursor. This result set contains
613: a superset of the updatable columns; the caller must verify that
614: only those listed in the FOR UPDATE clause are used.
615:
616: @return a result column list containing a description of
617: the target table (this may contain non-updatable columns).
618: * @exception StandardException Thrown on error
619: */
620: private ResultColumnDescriptor[] genTargetResultColList()
621: throws StandardException {
622: ResultColumnList newList;
623:
624: /*
625: updateTable holds the FromTable that is the target.
626: copy its ResultColumnList, making BaseColumn references
627: for use in the CurrentOfNode, which behaves as if it had
628: base columns for the statement it is in.
629:
630: updateTable is null if the cursor is not updatable.
631: */
632: if (updateTable == null)
633: return null;
634:
635: if (targetColumnDescriptors != null)
636: return targetColumnDescriptors;
637:
638: newList = (ResultColumnList) getNodeFactory().getNode(
639: C_NodeTypes.RESULT_COLUMN_LIST, getContextManager());
640: ResultColumnList rcl = updateTable.getResultColumns();
641: int rclSize = rcl.size();
642: for (int index = 0; index < rclSize; index++) {
643: ResultColumn origCol, newCol;
644: ValueNode newNode;
645:
646: origCol = (ResultColumn) rcl.elementAt(index);
647:
648: // Build a ResultColumn/BaseColumnNode pair for the column
649: newNode = (ValueNode) getNodeFactory().getNode(
650: C_NodeTypes.BASE_COLUMN_NODE,
651: origCol.getName(),
652: makeTableName(origCol.getSchemaName(), origCol
653: .getTableName()),
654: origCol.getTypeServices(), getContextManager());
655: newCol = (ResultColumn) getNodeFactory().getNode(
656: C_NodeTypes.RESULT_COLUMN,
657: origCol.columnDescriptor, newNode,
658: getContextManager());
659:
660: /* Build the ResultColumnList to return */
661: newList.addResultColumn(newCol);
662: }
663:
664: // we save the result so we only do this once
665: targetColumnDescriptors = newList.makeResultDescriptors();
666: return targetColumnDescriptors;
667: }
668:
669: /**
670: * Returns whether or not this Statement requires a set/clear savepoint
671: * around its execution. The following statement "types" do not require them:
672: * Cursor - unnecessary and won't work in a read only environment
673: * Xact - savepoint will get blown away underneath us during commit/rollback
674: *
675: * @return boolean Whether or not this Statement requires a set/clear savepoint
676: */
677: public boolean needsSavepoint() {
678: return false;
679: }
680:
681: /**
682: * Get information about this cursor. For sps,
683: * this is info saved off of the original query
684: * tree (the one for the underlying query).
685: *
686: * @return the cursor info
687: * @exception StandardException thrown if generation fails
688: */
689: public Object getCursorInfo() throws StandardException {
690: if (!needTarget)
691: return null;
692:
693: return new CursorInfo(updateMode, new CursorTableReference(
694: getUpdateExposedTableName(), getUpdateBaseTableName(),
695: getUpdateSchemaName()), genTargetResultColList(),
696: getUpdatableColumns());
697: }
698:
699: /**
700: Bind the update columns by their names to the target table
701: of the cursor specification.
702: Doesn't check for duplicates in the list, although it could...
703: REVISIT: If the list is empty, should it expand it out? at present,
704: it leaves it empty.
705:
706: @param targetTable The underlying target table
707:
708: @exception StandardException Thrown on error
709: */
710: private void bindUpdateColumns(FromTable targetTable)
711: throws StandardException {
712: int size = updatableColumns.size();
713: TableDescriptor tableDescriptor;
714: String columnName;
715: ResultColumnList rcls = resultSet.getResultColumns();
716:
717: for (int index = 0; index < size; index++) {
718: columnName = (String) updatableColumns.elementAt(index);
719: tableDescriptor = targetTable.getTableDescriptor();
720: if (tableDescriptor.getColumnDescriptor(columnName) == null) {
721: throw StandardException.newException(
722: SQLState.LANG_COLUMN_NOT_FOUND, columnName);
723: }
724:
725: ResultColumn rc;
726: //make sure that we are not using correlation names for updatable columns.
727: //eg select c11 as col1, 2, c13 as col3 from t1 for update of c11, c12
728: //In the eg above, correlation name for c11 will cause exception because Derby does not support correlation name for updatable columns
729: //But correlation name for c13 is ok because it is a read only column
730: for (int rclsIndex = 0; rclsIndex < rcls.size(); rclsIndex++) {//look through each column in the resultset for cursor
731: rc = ((ResultColumn) rcls.elementAt(rclsIndex));
732: if (rc.getSourceTableName() == null) //continue to look at the next column because this is derived column in the select list
733: continue;
734: if (rc.getExpression() != null
735: && rc.getExpression().getColumnName().equals(
736: columnName)
737: && !rc.getName().equals(columnName)) {
738: throw StandardException
739: .newException(
740: SQLState.LANG_CORRELATION_NAME_FOR_UPDATABLE_COLUMN_DISALLOWED_IN_CURSOR,
741: columnName);
742: }
743: }
744: }
745: }
746:
747: /**
748: * Get an array of strings for each updatable column
749: * in this list.
750: *
751: * @return an array of strings
752: */
753: private String[] getUpdateColumnNames() {
754: int size = updatableColumns.size();
755: if (size == 0) {
756: return (String[]) null;
757: }
758:
759: String[] names = new String[size];
760:
761: updatableColumns.copyInto(names);
762:
763: return names;
764: }
765:
766: public String getXML() {
767: return null;
768: }
769: }
|