Java tutorial
/* * Apache Derby is a subproject of the Apache DB project, and is licensed under * the Apache License, Version 2.0 (the "License"); you may not use these files * except in compliance with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. * * Splice Machine, Inc. has modified this file. * * All Splice Machine modifications are Copyright 2012 - 2016 Splice Machine, Inc., * and are licensed to you under the License; you may not use this file except in * compliance with the License. * * Unless required by applicable law or agreed to in writing, software distributed * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. * */ package com.splicemachine.db.impl.sql.compile; import com.splicemachine.db.catalog.types.DefaultInfoImpl; import com.splicemachine.db.iapi.error.StandardException; import com.splicemachine.db.iapi.reference.ClassName; import com.splicemachine.db.iapi.services.classfile.VMOpcode; import com.splicemachine.db.iapi.services.compiler.MethodBuilder; import com.splicemachine.db.iapi.services.sanity.SanityManager; import com.splicemachine.db.iapi.sql.ResultColumnDescriptor; import com.splicemachine.db.iapi.sql.ResultDescription; import com.splicemachine.db.iapi.sql.compile.*; import com.splicemachine.db.iapi.sql.conn.LanguageConnectionContext; import com.splicemachine.db.iapi.sql.dictionary.ColumnDescriptor; import com.splicemachine.db.iapi.sql.dictionary.DataDictionary; import com.splicemachine.db.iapi.sql.dictionary.DefaultDescriptor; import com.splicemachine.db.iapi.sql.dictionary.TableDescriptor; import com.splicemachine.db.iapi.store.access.TransactionController; import com.splicemachine.db.iapi.types.DataTypeDescriptor; import com.splicemachine.db.iapi.util.JBitSet; import java.util.*; import org.apache.commons.lang3.text.WordUtils; /** * A ResultSetNode represents a result set, that is, a set of rows. It is * analogous to a ResultSet in the LanguageModuleExternalInterface. In fact, * code generation for a a ResultSetNode will create a "new" call to a * constructor for a ResultSet. */ public abstract class ResultSetNode extends QueryTreeNode { protected int resultSetNumber = -1; /* Bit map of referenced tables under this ResultSetNode */ protected JBitSet referencedTableMap; protected ResultColumnList resultColumns; protected boolean statementResultSet; protected boolean cursorTargetTable; protected boolean insertSource; protected CostEstimate costEstimate; protected CostEstimate scratchCostEstimate; protected Optimizer optimizer; // Final cost estimate for this result set node, which is the estimate // for this node with respect to the best join order for the top-level // query. Subclasses will set this value where appropriate. protected CostEstimate finalCostEstimate; /** * Count the number of distinct aggregates in the list. * By 'distinct' we mean aggregates of the form: * <UL><I>SELECT MAX(DISTINCT x) FROM T<\I><\UL> * * @return number of aggregates */ protected static int numDistinctAggregates(List<AggregateNode> aggregateVector) { int count = 0; for (AggregateNode aggNode : aggregateVector) { count += (aggNode.isDistinct()) ? 1 : 0; } return count; } /** * @return true if this node is represented by a Sinking operation on * the execution side. */ public boolean isParallelizable() { return false; } /** * Convert this object to a String. See comments in QueryTreeNode.java * for how this should be done for tree printing. * * @return This object as a String */ public String toString() { if (SanityManager.DEBUG) { return "resultSetNumber: " + resultSetNumber + "\n" + "referencedTableMap: " + (referencedTableMap != null ? referencedTableMap.toString() : "null") + "\n" + "statementResultSet: " + statementResultSet + "\n" + super.toString(); } else { return ""; } } @Override public String toHTMLString() { return "resultSetNumber: " + resultSetNumber + "<br/>" + "referencedTableMap: " + Objects.toString(referencedTableMap) + "<br/>" + "statementResultSet: " + statementResultSet + "<br/>"; } /** * Prints the sub-nodes of this object. See QueryTreeNode.java for * how tree printing is supposed to work. * * @param depth The depth of this node in the tree */ public void printSubNodes(int depth) { if (SanityManager.DEBUG) { super.printSubNodes(depth); if (resultColumns != null) { printLabel(depth, "resultColumns: "); resultColumns.treePrint(depth + 1); } } } /** * Get the resultSetNumber in this ResultSetNode. Expected to be set during * generate(). * * @return int The resultSetNumber. */ public int getResultSetNumber() { return resultSetNumber; } /** * Get the CostEstimate for this ResultSetNode. * * @return The CostEstimate for this ResultSetNode. */ public CostEstimate getCostEstimate() { if (SanityManager.DEBUG) { if (costEstimate == null) { SanityManager.THROWASSERT("costEstimate is not expected to be null for " + getClass().getName()); } } return costEstimate; } /** * Get the final CostEstimate for this ResultSetNode. * * @return The final CostEstimate for this ResultSetNode. */ public CostEstimate getFinalCostEstimate() throws StandardException { if (SanityManager.DEBUG) { if (finalCostEstimate == null) { SanityManager .THROWASSERT("finalCostEstimate is not expected to be null for " + getClass().getName()); } } return finalCostEstimate; } /** * Assign the next resultSetNumber to the resultSetNumber in this ResultSetNode. * Expected to be done during generate(). * * @throws StandardException Thrown on error */ public void assignResultSetNumber() throws StandardException { // only set if currently unset if (resultSetNumber == -1) { resultSetNumber = getCompilerContext().getNextResultSetNumber(); resultColumns.setResultSetNumber(resultSetNumber); } } /** * Bind the non VTI tables in this ResultSetNode. This includes getting their * descriptors from the data dictionary and numbering them. * * @param dataDictionary The DataDictionary to use for binding * @param fromListParam FromList to use/append to. * @throws StandardException Thrown on error * @return ResultSetNode */ public ResultSetNode bindNonVTITables(DataDictionary dataDictionary, FromList fromListParam) throws StandardException { return this; } /** * Bind the VTI tables in this ResultSetNode. This includes getting their * descriptors from the data dictionary and numbering them. * * @param fromListParam FromList to use/append to. * @throws StandardException Thrown on error * @return ResultSetNode */ public ResultSetNode bindVTITables(FromList fromListParam) throws StandardException { return this; } /** * Bind the expressions in this ResultSetNode. This means binding the * sub-expressions, as well as figuring out what the return type is for * each expression. * * @param fromListParam FromList to use/append to. * @throws StandardException Thrown on error */ public void bindExpressions(FromList fromListParam) throws StandardException { if (SanityManager.DEBUG) SanityManager.ASSERT(false, "bindExpressions() is not expected to be called for " + this.getClass().toString()); } /** * Bind the expressions in this ResultSetNode if it has tables. This means binding the * sub-expressions, as well as figuring out what the return type is for * each expression. * * @param fromListParam FromList to use/append to. * @throws StandardException Thrown on error */ public void bindExpressionsWithTables(FromList fromListParam) throws StandardException { if (SanityManager.DEBUG) SanityManager.ASSERT(false, "bindExpressionsWithTables() is not expected to be called for " + this.getClass().toString()); } /** * Bind the expressions in the target list. This means binding the * sub-expressions, as well as figuring out what the return type is * for each expression. This is useful for EXISTS subqueries, where we * need to validate the target list before blowing it away and replacing * it with a SELECT true. * * @throws StandardException Thrown on error */ public void bindTargetExpressions(FromList fromListParam) throws StandardException { if (SanityManager.DEBUG) SanityManager.ASSERT(false, "bindTargetExpressions() is not expected to be called for " + this.getClass().toString()); } /** * Remember that this node is the source result set for an INSERT. */ public void setInsertSource() { insertSource = true; } /** * Verify that a SELECT * is valid for this type of subquery. * * @param outerFromList The FromList from the outer query block(s) * @param subqueryType The subquery type * @throws StandardException Thrown on error */ public void verifySelectStarSubquery(FromList outerFromList, int subqueryType) throws StandardException { if (SanityManager.DEBUG) SanityManager.ASSERT(false, "verifySelectStarSubquery() is not expected to be called for " + this.getClass().toString()); } /** * Expand "*" into a ResultColumnList with all of the columns * in the table's result list. * * @param allTableName The qualifier on the "*" * @return ResultColumnList The expanded list, or {@code null} if * {@code allTableName} is non-null and doesn't match a table name in * this result set * @throws StandardException Thrown on error */ public ResultColumnList getAllResultColumns(TableName allTableName) throws StandardException { if (SanityManager.DEBUG) SanityManager.THROWASSERT( "getAllResultColumns() not expected to be called for " + this.getClass().getName() + this); return null; } /** * Try to find a ResultColumn in the table represented by this FromTable * that matches the name in the given ColumnReference. * * @param columnReference The columnReference whose name we're looking * for in the given table. * @throws StandardException Thrown on error * @return A ResultColumn whose expression is the ColumnNode * that matches the ColumnReference. * Returns null if there is no match. */ public ResultColumn getMatchingColumn(ColumnReference columnReference) throws StandardException { if (SanityManager.DEBUG) SanityManager.THROWASSERT("getMatchingColumn() not expected to be called for " + this); return null; } /** * Set the result column for the subquery to a boolean true, * Useful for transformations such as * changing: * where exists (select ... from ...) * to: * where (select true from ...) * <p/> * NOTE: No transformation is performed if the ResultColumn.expression is * already the correct boolean constant. * * @param onlyConvertAlls Boolean, whether or not to just convert *'s * @return ResultSetNode whose resultColumn was transformed; defaults * to "this" here, but can be overridden by subclasses. * @throws StandardException Thrown on error */ public ResultSetNode setResultToBooleanTrueNode(boolean onlyConvertAlls) throws StandardException { BooleanConstantNode booleanNode; ResultColumn resultColumn; /* We need to be able to handle both ResultColumn and AllResultColumn * since they are peers. */ if (resultColumns.elementAt(0) instanceof AllResultColumn) { resultColumn = (ResultColumn) getNodeFactory().getNode(C_NodeTypes.RESULT_COLUMN, "", null, getContextManager()); } else if (onlyConvertAlls) { return this; } else { resultColumn = (ResultColumn) resultColumns.elementAt(0); /* Nothing to do if query is already select TRUE ... */ if (resultColumn.getExpression().isBooleanTrue() && resultColumns.size() == 1) { return this; } } booleanNode = (BooleanConstantNode) getNodeFactory().getNode(C_NodeTypes.BOOLEAN_CONSTANT_NODE, Boolean.TRUE, getContextManager()); resultColumn.setExpression(booleanNode); resultColumn.setType(booleanNode.getTypeServices()); /* VirtualColumnIds are 1-based, RCLs are 0-based */ resultColumn.setVirtualColumnId(1); resultColumns.setElementAt(resultColumn, 0); return this; } /** * Get the FromList. Create and return an empty FromList. (Subclasses * which actuall have FromLists will override this.) This is useful because * there is a FromList parameter to bindExpressions() which is used as * the common FromList to bind against, allowing us to support * correlation columns under unions in subqueries. * * @return FromList * @throws StandardException Thrown on error */ public FromList getFromList() throws StandardException { return (FromList) getNodeFactory().getNode(C_NodeTypes.FROM_LIST, getNodeFactory().doJoinOrderOptimization(), getContextManager()); } /** * Bind the result columns of this ResultSetNode when there is no * base table to bind them to. This is useful for SELECT statements, * where the result columns get their types from the expressions that * live under them. * * @param fromListParam FromList to use/append to. * @throws StandardException Thrown on error */ public void bindResultColumns(FromList fromListParam) throws StandardException { resultColumns.bindResultColumnsToExpressions(); } /** * Bind the result columns for this ResultSetNode to a base table. * This is useful for INSERT and UPDATE statements, where the * result columns get their types from the table being updated or * inserted into. * If a result column list is specified, then the verification that the * result column list does not contain any duplicates will be done when * binding them by name. * * @param targetTableDescriptor The TableDescriptor for the table being * updated or inserted into * @param targetColumnList For INSERT statements, the user * does not have to supply column * names (for example, "insert into t * values (1,2,3)". When this * parameter is null, it means that * the user did not supply column * names, and so the binding should * be done based on order. When it * is not null, it means do the binding * by name, not position. * @param statement Calling DMLStatementNode (Insert or Update) * @param fromListParam FromList to use/append to. * @throws StandardException Thrown on error */ public void bindResultColumns(TableDescriptor targetTableDescriptor, FromVTI targetVTI, ResultColumnList targetColumnList, DMLStatementNode statement, FromList fromListParam) throws StandardException { /* For insert select, we need to expand any *'s in the * select before binding the result columns */ if (this instanceof SelectNode) { resultColumns.expandAllsAndNameColumns(((SelectNode) this).fromList); } /* If specified, copy the result column names down to the * source's target list. */ if (targetColumnList != null) { resultColumns.copyResultColumnNames(targetColumnList); } if (targetColumnList != null) { if (targetTableDescriptor != null) { resultColumns.bindResultColumnsByName(targetTableDescriptor, (DMLStatementNode) statement); } else { resultColumns.bindResultColumnsByName(targetVTI.getResultColumns(), targetVTI, statement); } } else resultColumns.bindResultColumnsByPosition(targetTableDescriptor); } /** * Bind untyped nulls to the types in the given ResultColumnList. * This is used for binding the nulls in row constructors and * table constructors. In all other cases (as of the time of * this writing), we do nothing. * * @param rcl The ResultColumnList with the types to bind nulls to * @throws StandardException Thrown on error */ public void bindUntypedNullsToResultColumns(ResultColumnList rcl) throws StandardException { } /** * Preprocess a ResultSetNode - this currently means: * o Generating a referenced table map for each ResultSetNode. * o Putting the WHERE and HAVING clauses in conjunctive normal form (CNF). * o Converting the WHERE and HAVING clauses into PredicateLists and * classifying them. * o Ensuring that a ProjectRestrictNode is generated on top of every * FromBaseTable and generated in place of every FromSubquery. * o Pushing single table predicates down to the new ProjectRestrictNodes. * * @param numTables The number of tables in the DML Statement * @param gbl The group by list, if any * @param fromList The from list, if any * @return ResultSetNode at top of preprocessed tree. * @throws StandardException Thrown on error */ public ResultSetNode preprocess(int numTables, GroupByList gbl, FromList fromList) throws StandardException { if (SanityManager.DEBUG) SanityManager.THROWASSERT("preprocess() not expected to be called for " + getClass().toString()); return null; } /** * Ensure that the top of the RSN tree has a PredicateList. * * @param numTables The number of tables in the query. * @return ResultSetNode A RSN tree with a node which has a PredicateList on top. * @throws StandardException Thrown on error */ public ResultSetNode ensurePredicateList(int numTables) throws StandardException { if (SanityManager.DEBUG) SanityManager .THROWASSERT("ensurePredicateList() not expected to be called for " + getClass().toString()); return null; } /** * Add a new predicate to the list. This is useful when doing subquery * transformations, when we build a new predicate with the left side of * the subquery operator and the subquery's result column. * * @param predicate The predicate to add * @return ResultSetNode The new top of the tree. * @throws StandardException Thrown on error */ public ResultSetNode addNewPredicate(Predicate predicate) throws StandardException { if (SanityManager.DEBUG) SanityManager.THROWASSERT("addNewPredicate() not expected to be called for " + getClass().toString()); return null; } /** * Evaluate whether or not the subquery in a FromSubquery is flattenable. * Currently, a FSqry is flattenable if all of the following are true: * o Subquery is a SelectNode. (ie, not a RowResultSetNode or a UnionNode) * o It contains no top level subqueries. (RESOLVE - we can relax this) * o It does not contain a group by or having clause * o It does not contain aggregates. * * @param fromList The outer from list * @return boolean Whether or not the FromSubquery is flattenable. */ public boolean flattenableInFromSubquery(FromList fromList) { if (SanityManager.DEBUG) SanityManager.THROWASSERT( "flattenableInFromSubquery() not expected to be called for " + getClass().toString()); return false; } /** * Optimize a ResultSetNode. This means choosing the best access * path for each table under the ResultSetNode, among other things. * <p/> * The only RSNs that need to implement their own optimize() are a * SelectNode and those RSNs that can appear above a SelectNode in the * query tree. Currently, a ProjectRestrictNode is the only RSN that * can appear above a SelectNode. * * @param dataDictionary The DataDictionary to use for optimization * @param predicates The PredicateList to apply. * @param outerRows The number of outer joining rows * @throws StandardException Thrown on error * @return ResultSetNode The top of the optimized query tree */ public ResultSetNode optimize(DataDictionary dataDictionary, PredicateList predicates, double outerRows) throws StandardException { if (SanityManager.DEBUG) SanityManager.ASSERT(false, "optimize() is not expected to be called for " + this.getClass().toString()); return null; } /** * Modify the access paths according to the decisions the optimizer * made. This can include adding project/restrict nodes, * index-to-base-row nodes, etc. * * @throws StandardException Thrown on error * @return The modified query tree */ public ResultSetNode modifyAccessPaths() throws StandardException { /* Default behavior is to do nothing */ return this; } /** * Modify the access paths according to the decisions the optimizer * made. This can include adding project/restrict nodes, * index-to-base-row nodes, etc. * * @param predList A list of optimizable predicates that should * be pushed to this ResultSetNode, as determined by optimizer. * @return The modified query tree * @throws StandardException Thrown on error */ public ResultSetNode modifyAccessPaths(PredicateList predList) throws StandardException { // Default behavior is to call the no-arg version of this method. return modifyAccessPaths(); } public ResultColumnDescriptor[] makeResultDescriptors() { return resultColumns.makeResultDescriptors(); } /** * Get the resultColumns for this ResultSetNode * * @return ResultColumnList for this ResultSetNode */ public ResultColumnList getResultColumns() { return resultColumns; } /** * Set the resultColumns in this ResultSetNode * * @param newRCL The new ResultColumnList for this ResultSetNode */ public void setResultColumns(ResultColumnList newRCL) { resultColumns = newRCL; } /* ** Check whether the column lengths and types of the result columns ** match the expressions under those columns. This is useful for ** INSERT and UPDATE statements. For SELECT statements this method ** should always return true. There is no need to call this for a ** DELETE statement. ** ** @return true means all the columns match their expressions, ** false means at least one column does not match its ** expression */ /** * Get the referencedTableMap for this ResultSetNode * * @return JBitSet Referenced table map for this ResultSetNode */ public JBitSet getReferencedTableMap() { return referencedTableMap; } /** * Set the referencedTableMap in this ResultSetNode * * @param newRTM The new referencedTableMap for this ResultSetNode */ public void setReferencedTableMap(JBitSet newRTM) { referencedTableMap = newRTM; } /** * Fill the referencedTableMap with this ResultSetNode. * * @param passedMap The table map to fill in. */ public void fillInReferencedTableMap(JBitSet passedMap) { } /** * Check for (and reject) ? parameters directly under the ResultColumns. * This is done for SELECT statements. * * @throws StandardException Thrown if a ? parameter found * directly under a ResultColumn */ public void rejectParameters() throws StandardException { /* Okay if no resultColumns yet - means no parameters there */ if (resultColumns != null) { resultColumns.rejectParameters(); } } /** * Check for (and reject) XML values directly under the ResultColumns. * This is done for SELECT/VALUES statements. We reject values * in this case because JDBC does not define an XML type/binding * and thus there's no standard way to pass such a type back * to a JDBC application. * * @throws StandardException Thrown if an XML value found * directly under a ResultColumn */ public void rejectXMLValues() throws StandardException { if (resultColumns != null) { resultColumns.rejectXMLValues(); } } /** * Rename generated result column names as '1', '2' etc... These will be the result * column names seen by JDBC clients. */ public void renameGeneratedResultNames() throws StandardException { for (int i = 0; i < resultColumns.size(); i++) { ResultColumn rc = (ResultColumn) resultColumns.elementAt(i); if (rc.isNameGenerated()) rc.setName(Integer.toString(i + 1)); } } /** * This method is overridden to allow a resultset node to know * if it is the one controlling the statement -- i.e., it is * the outermost result set node for the statement. */ public void markStatementResultSet() { statementResultSet = true; } /** * Parse a default and turn it into a query tree. * * @throws StandardException Thrown on failure * @param defaultText Text of Default. * @return The parsed default as a query tree. */ public ValueNode parseDefault(String defaultText) throws StandardException { Parser p; ValueNode defaultTree; LanguageConnectionContext lcc = getLanguageConnectionContext(); CompilerContext compilerContext = getCompilerContext(); /* Get a Statement to pass to the parser */ /* We're all set up to parse. We have to build a compilable SQL statement * before we can parse - So, we goober up a VALUES defaultText. */ String values = "VALUES " + defaultText; /* ** Get a new compiler context, so the parsing of the select statement ** doesn't mess up anything in the current context (it could clobber ** the ParameterValueSet, for example). */ CompilerContext newCC = lcc.pushCompilerContext(); p = newCC.getParser(); /* Finally, we can call the parser */ // Since this is always nested inside another SQL statement, so topLevel flag // should be false Visitable qt = p.parseStatement(values); if (SanityManager.DEBUG) { if (!(qt instanceof CursorNode)) { SanityManager .THROWASSERT("qt expected to be instanceof CursorNode, not " + qt.getClass().getName()); } CursorNode cn = (CursorNode) qt; if (!(cn.getResultSetNode() instanceof RowResultSetNode)) { SanityManager.THROWASSERT("cn.getResultSetNode() expected to be instanceof RowResultSetNode, not " + cn.getResultSetNode().getClass().getName()); } } defaultTree = ((ResultColumn) ((CursorNode) qt).getResultSetNode().getResultColumns().elementAt(0)) .getExpression(); lcc.popCompilerContext(newCC); return defaultTree; } /** * Make a ResultDescription for use in a ResultSet. * This is useful when generating/executing a NormalizeResultSet, since * it can appear anywhere in the tree. * * @return A ResultDescription for this ResultSetNode. */ public ResultDescription makeResultDescription() { ResultColumnDescriptor[] colDescs = makeResultDescriptors(); return getExecutionFactory().getResultDescription(colDescs, null); } /** * Mark this ResultSetNode as the target table of an updatable * cursor. Most types of ResultSetNode can't be target tables. * * @return true if the target table supports positioned updates. */ public boolean markAsCursorTargetTable() { return false; } /** * Put a ProjectRestrictNode on top of this ResultSetNode. * ColumnReferences must continue to point to the same ResultColumn, so * that ResultColumn must percolate up to the new PRN. However, * that ResultColumn will point to a new expression, a VirtualColumnNode, * which points to the FromTable and the ResultColumn that is the source for * the ColumnReference. * (The new PRN will have the original of the ResultColumnList and * the ResultColumns from that list. The FromTable will get shallow copies * of the ResultColumnList and its ResultColumns. ResultColumn.expression * will remain at the FromTable, with the PRN getting a new * VirtualColumnNode for each ResultColumn.expression.) * <p/> * This is useful for UNIONs, where we want to generate a DistinctNode above * the UnionNode to eliminate the duplicates, because DistinctNodes expect * their immediate child to be a PRN. * * @return The generated ProjectRestrictNode atop the original ResultSetNode. * @throws StandardException Thrown on error */ public ResultSetNode genProjectRestrict() throws StandardException { /* We get a shallow copy of the ResultColumnList and its * ResultColumns. (Copy maintains ResultColumn.expression for now.) */ ResultColumnList prRCList = resultColumns; resultColumns = resultColumns.copyListAndObjects(); /* Replace ResultColumn.expression with new VirtualColumnNodes * in the ProjectRestrictNode's ResultColumnList. (VirtualColumnNodes include * pointers to source ResultSetNode, this, and source ResultColumn.) */ prRCList.genVirtualColumnNodes(this, resultColumns); /* Finally, we create the new ProjectRestrictNode */ return (ResultSetNode) getNodeFactory().getNode(C_NodeTypes.PROJECT_RESTRICT_NODE, this, prRCList, null, /* Restriction */ null, /* Restriction as PredicateList */ null, /* Project subquery list */ null, /* Restrict subquery list */ null, getContextManager()); } /** * Generate the code for a NormalizeResultSet. * The call must push two items before calling this method * <OL> * <LI> pushGetResultSetFactoryExpression * <LI> the expression to normalize * </OL> * * @param acb The ActivationClassBuilder * @param mb The method to put the generated code in * @param resultSetNumber The result set number for the NRS * @param resultDescription The ERD for the ResultSet * @throws StandardException Thrown on error */ public void generateNormalizationResultSet(ActivationClassBuilder acb, MethodBuilder mb, int resultSetNumber, ResultDescription resultDescription) throws StandardException { int erdNumber = acb.addItem(resultDescription); // instance and first arg are pushed by caller mb.push(resultSetNumber); mb.push(erdNumber); mb.push(getCostEstimate().rowCount()); mb.push(getCostEstimate().getEstimatedCost()); mb.push(false); // The only use of this method is from UnionNode, which passes in ProjectRestrictNodes // as 'callerNode' for left and right of the Union. However, we invoke getNormalizeResultSet // here and end up with a NormalizeOperation. To avoid putting the explain plan of // the Union or the ProjectRestricts, for now leave the explain plan blank // on the implicitly instantiated NormalizeOperation. mb.push(""); // blank explain plan mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, "getNormalizeResultSet", ClassName.NoPutResultSet, 7); } /** * The optimizer's decision on the access path for a result set * may require the generation of extra result sets. For example, * if it chooses an index for a FromBaseTable, we need an IndexToBaseRowNode * above the FromBaseTable (and the FromBaseTable has to change its * column list to match the index. * <p/> * This method in the parent class does not generate any extra result sets. * It may be overridden in child classes. * * @throws StandardException Thrown on error * @return A ResultSetNode tree modified to do any extra processing for * the chosen access path */ public ResultSetNode changeAccessPath() throws StandardException { return this; } /** * Search to see if a query references the specifed table name. * * @param name Table name (String) to search for. * @param baseTable Whether or not name is for a base table * @throws StandardException Thrown on error * @return true if found, else false */ public boolean referencesTarget(String name, boolean baseTable) throws StandardException { return false; } /** * Return whether or not the underlying ResultSet tree will return * a single row, at most. * This is important for join nodes where we can save the extra next * on the right side if we know that it will return at most 1 row. * * @return Whether or not the underlying ResultSet tree will return a single row. * @throws StandardException Thrown on error */ public boolean isOneRowResultSet() throws StandardException { // Default is false return false; } /** * Return whether or not the underlying ResultSet tree is for a NOT EXISTS * join. * * @return Whether or not the underlying ResultSet tree if for NOT EXISTS. */ public boolean isNotExists() { // Default is false return false; } /** * Accept the visitor for all visitable children of this node. * * @param v the visitor */ @Override public void acceptChildren(Visitor v) throws StandardException { super.acceptChildren(v); if (resultColumns != null) { resultColumns = (ResultColumnList) resultColumns.accept(v, this); } } /** * Consider materialization for this ResultSet tree if it is valid and cost effective * (It is not valid if incorrect results would be returned.) * * @return Top of the new/same ResultSet tree. * @throws StandardException Thrown on error */ public ResultSetNode considerMaterialization(JBitSet outerTables) throws StandardException { return this; } /** * Return whether or not to materialize this ResultSet tree. * * @return Whether or not to materialize this ResultSet tree. * would return valid results. * @throws StandardException Thrown on error */ public boolean performMaterialization(JBitSet outerTables) throws StandardException { return false; } /** * General logic shared by Core compilation and by the Replication Filter * compiler. A couple ResultSets (the ones used by PREPARE SELECT FILTER) * implement this method. * * @param acb The ExpressionClassBuilder for the class being built * @param mb The method the expression will go into * @throws StandardException Thrown on error */ public void generateResultSet(ExpressionClassBuilder acb, MethodBuilder mb) throws StandardException { System.out.println("I am a " + getClass()); if (SanityManager.DEBUG) SanityManager.NOTREACHED(); return; } /** * Get the lock mode for the target of an update statement * (a delete or update). The update mode will always be row for * CurrentOfNodes. It will be table if there is no where clause. * * @return The lock mode * @see TransactionController */ public int updateTargetLockMode() { return TransactionController.MODE_TABLE; } // It may be we have a SELECT view underneath a LOJ. // Return null for now.. we don't do any optimization. public JBitSet LOJgetReferencedTables(int numTables) throws StandardException { if (this instanceof FromTable) { if (((FromTable) this).tableNumber != -1) { JBitSet map = new JBitSet(numTables); map.set(((FromTable) this).tableNumber); return map; } } return null; } /** * Put a ProjectRestrictNode on top of each FromTable in the FromList. * ColumnReferences must continue to point to the same ResultColumn, so * that ResultColumn must percolate up to the new PRN. However, * that ResultColumn will point to a new expression, a VirtualColumnNode, * which points to the FromTable and the ResultColumn that is the source for * the ColumnReference. * (The new PRN will have the original of the ResultColumnList and * the ResultColumns from that list. The FromTable will get shallow copies * of the ResultColumnList and its ResultColumns. ResultColumn.expression * will remain at the FromTable, with the PRN getting a new * VirtualColumnNode for each ResultColumn.expression.) * We then project out the non-referenced columns. If there are no referenced * columns, then the PRN's ResultColumnList will consist of a single ResultColumn * whose expression is 1. * * @param numTables Number of tables in the DML Statement * @return The generated ProjectRestrictNode atop the original FromTable. * @throws StandardException Thrown on error */ protected ResultSetNode genProjectRestrict(int numTables) throws StandardException { return genProjectRestrict(); } /** * Get an optimizer to use for this ResultSetNode. Only get it once - * subsequent calls return the same optimizer. * * @throws StandardException Thrown on error */ protected Optimizer getOptimizer(OptimizableList optList, OptimizablePredicateList predList, DataDictionary dataDictionary, RequiredRowOrdering requiredRowOrdering) throws StandardException { if (optimizer == null) { /* Get an optimizer. */ LanguageConnectionContext lcc = getLanguageConnectionContext(); OptimizerFactory optimizerFactory = lcc.getOptimizerFactory(); int numTables = getCompilerContext().getNumTables(); optimizer = optimizerFactory.getOptimizer(optList, predList, dataDictionary, requiredRowOrdering, numTables, lcc); } optimizer.prepForNextRound(); return optimizer; } /** * Get the optimizer for this result set. * * @return If this.optimizer has has already been created by the * getOptimizer() method above, then return it; otherwise, * return null. */ protected Optimizer getOptimizerImpl() { // Note that the optimizer might be null because it's possible that // we'll get here before any calls to getOptimizer() were made, which // can happen if we're trying to save a "best path" but we haven't // actually found one yet. In that case we just return the "null" // value; the caller must check for it and behave appropriately. // Ex. see TableOperatorNode.addOrLoadBestPlanMapping(). return optimizer; } /** * Get a cost estimate to use for this ResultSetNode. * * @throws StandardException Thrown on error */ protected CostEstimate getNewCostEstimate() throws StandardException { OptimizerFactory optimizerFactory = getLanguageConnectionContext().getOptimizerFactory(); return optimizerFactory.getCostEstimate(); } /** * Determine whether or not the specified name is an exposed name in * the current query block. * * @param name The specified name to search for as an exposed name. * @param schemaName Schema name, if non-null. * @param exactMatch Whether or not we need an exact match on specified schema and table * names or match on table id. * @return The FromTable, if any, with the exposed name. * @throws StandardException Thrown on error */ protected FromTable getFromTableByName(String name, String schemaName, boolean exactMatch) throws StandardException { if (SanityManager.DEBUG) { SanityManager.THROWASSERT("getFromTableByName() not expected to be called for " + getClass().getName()); } return null; } /** * Set the type of each parameter in the result column list for this * table constructor. * * @param typeColumns The ResultColumnList containing the desired result * types. * @throws StandardException Thrown on error */ void setTableConstructorTypes(ResultColumnList typeColumns) throws StandardException { // VALUES clause needs special handling that's taken care of in a // sub-class. For all other nodes, just go through the result columns // and set the type for dynamic parameters. for (int i = 0; i < resultColumns.size(); i++) { ResultColumn rc = (ResultColumn) resultColumns.elementAt(i); ValueNode re = rc.getExpression(); if (re != null && re.requiresTypeFromContext()) { ResultColumn typeCol = (ResultColumn) typeColumns.elementAt(i); re.setType(typeCol.getTypeServices()); } } } /** * Find the unreferenced result columns and project them out. */ void projectResultColumns() throws StandardException { // It is only necessary for joins } /** * Get a parent ProjectRestrictNode above us. * This is useful when we need to preserve the * user specified column order when reordering the * columns in the distinct when we combine * an order by with a distinct. * * @return A parent ProjectRestrictNode to do column reordering * @throws StandardException Thrown on error */ ResultSetNode genProjectRestrictForReordering() throws StandardException { /* We get a shallow copy of the ResultColumnList and its * ResultColumns. (Copy maintains ResultColumn.expression for now.) */ ResultColumnList prRCList = resultColumns; resultColumns = resultColumns.copyListAndObjects(); /* Replace ResultColumn.expression with new VirtualColumnNodes * in the ProjectRestrictNode's ResultColumnList. (VirtualColumnNodes include * pointers to source ResultSetNode, this, and source ResultColumn.) * NOTE: We don't want to mark the underlying RCs as referenced, otherwise * we won't be able to project out any of them. */ prRCList.genVirtualColumnNodes(this, resultColumns, false); /* Finally, we create the new ProjectRestrictNode */ return (ResultSetNode) getNodeFactory().getNode(C_NodeTypes.PROJECT_RESTRICT_NODE, this, prRCList, null, /* Restriction */ null, /* Restriction as PredicateList */ null, /* Project subquery list */ null, /* Restrict subquery list */ null, getContextManager()); } boolean columnTypesAndLengthsMatch() throws StandardException { return resultColumns.columnTypesAndLengthsMatch(); } /** * This ResultSet is the source for an Insert. The target RCL * is in a different order and/or a superset of this RCL. In most cases * we will add a ProjectRestrictNode on top of the source with an RCL that * matches the target RCL. * NOTE - The new or enhanced RCL will be fully bound. * * @param target the target node for the insert * @param inOrder are source cols in same order as target cols? * @param colMap int array representation of correspondence between * RCLs - colmap[i] = -1 -> missing in current RCL * colmap[i] = j -> targetRCL(i) <-> thisRCL(j+1) * @return a node that replaces this node and whose RCL matches the target * RCL. May return this node if no changes to the RCL are needed, or if the * RCL is modified in-place. * @throws StandardException Thrown on error */ ResultSetNode enhanceRCLForInsert(InsertNode target, boolean inOrder, int[] colMap) throws StandardException { if (!inOrder || resultColumns.visibleSize() < target.resultColumnList.size()) { return generateProjectRestrictForInsert(target, colMap); } return this; } /** * Generate an RCL that can replace the original RCL of this node to * match the RCL of the target for the insert. * * @param target the target node for the insert * @param colMap int array representation of correspondence between * RCLs - colmap[i] = -1 -> missing in current RCL * colmap[i] = j -> targetRCL(i) <-> thisRCL(j+1) * @return an RCL that matches the target RCL */ ResultColumnList getRCLForInsert(InsertNode target, int[] colMap) throws StandardException { // our newResultCols are put into the bound form straight away. ResultColumnList newResultCols = (ResultColumnList) getNodeFactory().getNode(C_NodeTypes.RESULT_COLUMN_LIST, getContextManager()); /* Create a massaged version of the source RCL. * (Much simpler to build new list and then assign to source, * rather than massage the source list in place.) */ int numTargetColumns = target.resultColumnList.size(); for (int index = 0; index < numTargetColumns; index++) { ResultColumn newResultColumn = null; if (colMap[index] != -1) { // getResultColumn uses 1-based positioning, so offset the colMap entry appropriately newResultColumn = resultColumns.getResultColumn(colMap[index] + 1); } else { newResultColumn = genNewRCForInsert(target.targetTableDescriptor, target.targetVTI, index + 1, target.getDataDictionary()); } newResultCols.addResultColumn(newResultColumn); } return newResultCols; } /** * Determine if this result set is updatable or not, for a cursor * (i.e., is it a cursor-updatable select). This returns false * and we expect selectnode to refine it for further checking. * * @throws StandardException Thrown on error */ boolean isUpdatableCursor(DataDictionary dd) throws StandardException { if (SanityManager.DEBUG) SanityManager.DEBUG("DumpUpdateCheck", "cursor is not a select result set"); return false; } /** * return the target table of an updatable cursor result set. * since this is not updatable, just return null. */ FromTable getCursorTargetTable() { return null; } /** * Mark this ResultSetNode as *not* the target table of an updatable * cursor. */ void notCursorTargetTable() { cursorTargetTable = false; } /** * Return whether or not this ResultSetNode contains a subquery with a * reference to the specified target. * * @param name The table name. * @return boolean Whether or not a reference to the table was found. * @throws StandardException Thrown on error */ boolean subqueryReferencesTarget(String name, boolean baseTable) throws StandardException { return false; } /** * Decrement (query block) level (0-based) for * all of the tables in this ResultSet tree. * This is useful when flattening a subquery. * * @param decrement The amount to decrement by. */ abstract void decrementLevel(int decrement); /** * Push the order by list down from the cursor node * into its child result set so that the optimizer * has all of the information that it needs to * consider sort avoidance. * * @param orderByList The order by list */ void pushOrderByList(OrderByList orderByList) { if (SanityManager.DEBUG) { SanityManager.THROWASSERT("pushOrderByList() not expected to be called for " + getClass().getName()); } } /** * Push down the offset and fetch first parameters, if any. This method * should be overridden by the result sets that need this. * * @param offset the OFFSET, if any * @param fetchFirst the OFFSET FIRST, if any * @param hasJDBClimitClause true if the clauses were added by (and have the semantics of) a JDBC limit clause */ void pushOffsetFetchFirst(ValueNode offset, ValueNode fetchFirst, boolean hasJDBClimitClause) { if (SanityManager.DEBUG) { SanityManager .THROWASSERT("pushOffsetFetchFirst() not expected to be called for " + getClass().getName()); } } /** * Mark this node and its children as not being a flattenable join. */ void notFlattenableJoin() { } /** * Return whether or not the underlying ResultSet tree * is ordered on the specified columns. * RESOLVE - This method currently only considers the outermost table * of the query block. * * @throws StandardException Thrown on error * @param crs The specified ColumnReference[] * @param permuteOrdering Whether or not the order of the CRs in the array can be permuted * @param fbtVector Vector that is to be filled with the FromBaseTable * @return Whether the underlying ResultSet tree * is ordered on the specified column. */ boolean isOrderedOn(ColumnReference[] crs, boolean permuteOrdering, Vector fbtVector) throws StandardException { return false; } /** * Return whether or not this ResultSet tree is guaranteed to return * at most 1 row based on heuristics. (A RowResultSetNode and a * SELECT with a non-grouped aggregate will return at most 1 row.) * * @return Whether or not this ResultSet tree is guaranteed to return * at most 1 row based on heuristics. */ boolean returnsAtMostOneRow() { return false; } /** * Replace any DEFAULTs with the associated tree for the default if * allowed, or flag (when inside top level set operator nodes). Subqueries * are checked for illegal DEFAULTs elsewhere. * * @param ttd The TableDescriptor for the target table. * @param tcl The RCL for the target table. * @param allowDefaults true if allowed * @throws StandardException Thrown on error */ void replaceOrForbidDefaults(TableDescriptor ttd, ResultColumnList tcl, boolean allowDefaults) throws StandardException { if (SanityManager.DEBUG) { SanityManager.THROWASSERT( "replaceOrForbidDefaults() not expected to be called for " + this.getClass().getName()); } } /** * Is it possible to do a distinct scan on this ResultSet tree. * (See SelectNode for the criteria.) * * @param distinctColumns the set of distinct columns * @return Whether or not it is possible to do a distinct scan on this ResultSet tree. */ boolean isPossibleDistinctScan(Set<BaseColumnNode> distinctColumns) { return false; } /** * Mark the underlying scan as a distinct scan. */ void markForDistinctScan() throws StandardException { if (SanityManager.DEBUG) { SanityManager .THROWASSERT("markForDistinctScan() not expected to be called for " + getClass().getName()); } } /** * Notify the underlying result set tree that the optimizer has chosen * to "eliminate" a sort. Sort elimination can happen as part of * preprocessing (see esp. SelectNode.preprocess(...)) or it can happen * if the optimizer chooses an access path that inherently returns the * rows in the correct order (also known as a "sort avoidance" plan). * In either case we drop the sort and rely on the underlying result set * tree to return its rows in the correct order. * <p/> * For most types of ResultSetNodes we automatically get the rows in the * correct order if the sort was eliminated. One exception to this rule, * though, is the case of an IndexRowToBaseRowNode, for which we have * to disable bulk fetching on the underlying base table. Otherwise * the index scan could return rows out of order if the base table is * updated while the scan is "in progress" (i.e. while the result set * is open). * <p/> * In order to account for this (and potentially other, similar issues * in the future) this method exists to notify the result set node that * it is expected to return rows in the correct order. The result set * can then take necessary action to satsify this requirement--such as * disabling bulk fetch in the case of IndexRowToBaseRowNode. * <p/> * All of that said, any ResultSetNodes for which we could potentially * eliminate sorts should override this method accordingly. So we don't * ever expect to get here. */ void adjustForSortElimination() { if (SanityManager.DEBUG) { SanityManager.THROWASSERT( "adjustForSortElimination() not expected to be called for " + getClass().getName()); } } /** * Same goal as adjustForSortElimination above, but this version * takes a RequiredRowOrdering to allow nodes to adjust based on * the ORDER BY clause, if needed. */ void adjustForSortElimination(RequiredRowOrdering rowOrdering) throws StandardException { /* Default is to ignore the row ordering; subclasses must * override if they need to use it. */ adjustForSortElimination(); } void optimizeTrace(OptimizerFlag flag, int intParam1, int intParam2, double doubleParam, Object... objectParams) { Optimizer optimizer = this.getOptimizerImpl(); if (optimizer != null) { optimizer.tracer().trace(flag, intParam1, intParam2, doubleParam, objectParams); } } /** * Generate the RC/expression for an unspecified column in an insert. * Use the default if one exists. * * @param targetTD Target TableDescriptor if the target is not a VTI, null if a VTI. * @param targetVTI Target description if it is a VTI, null if not a VTI * @param columnNumber The column number * @param dataDictionary The DataDictionary * @throws StandardException Thrown on error * @return The RC/expression for the unspecified column. */ private ResultColumn genNewRCForInsert(TableDescriptor targetTD, FromVTI targetVTI, int columnNumber, DataDictionary dataDictionary) throws StandardException { ResultColumn newResultColumn = null; // the i-th column's value was not specified, so create an // expression containing its default value (null for now) // REVISIT: will we store trailing nulls? if (targetVTI != null) { newResultColumn = targetVTI.getResultColumns().getResultColumn(columnNumber); newResultColumn = newResultColumn.cloneMe(); newResultColumn.setExpressionToNullNode(); } else { // column position is 1-based, index is 0-based. ColumnDescriptor colDesc = targetTD.getColumnDescriptor(columnNumber); DataTypeDescriptor colType = colDesc.getType(); // Check for defaults DefaultInfoImpl defaultInfo = (DefaultInfoImpl) colDesc.getDefaultInfo(); //Column has constant default value , //if it have defaultInfo and not be autoincrement. if (defaultInfo != null && !colDesc.isAutoincrement()) { //RESOLVEPARAMETER - skip the tree if we have the value /* if (defaultInfo.getDefaultValue() != null) { } else */ { if (colDesc.hasGenerationClause()) { // later on we will revisit the generated columns and bind // their generation clauses newResultColumn = createGeneratedColumn(targetTD, colDesc); } else { // Generate the tree for the default String defaultText = defaultInfo.getDefaultText(); ValueNode defaultTree = parseDefault(defaultText); defaultTree = defaultTree.bindExpression(getFromList(), (SubqueryList) null, (Vector) null); newResultColumn = (ResultColumn) getNodeFactory().getNode(C_NodeTypes.RESULT_COLUMN, defaultTree.getTypeServices(), defaultTree, getContextManager()); } DefaultDescriptor defaultDescriptor = colDesc.getDefaultDescriptor(dataDictionary); if (SanityManager.DEBUG) { SanityManager.ASSERT(defaultDescriptor != null, "defaultDescriptor expected to be non-null"); } getCompilerContext().createDependency(defaultDescriptor); } } else if (colDesc.isAutoincrement()) { newResultColumn = (ResultColumn) getNodeFactory().getNode(C_NodeTypes.RESULT_COLUMN, colDesc, null, getContextManager()); newResultColumn.setAutoincrementGenerated(); } else { newResultColumn = (ResultColumn) getNodeFactory().getNode(C_NodeTypes.RESULT_COLUMN, colType, getNullNode(colType), getContextManager()); } } // Mark the new RC as generated for an unmatched column in an insert newResultColumn.markGeneratedForUnmatchedColumnInInsert(); return newResultColumn; } /** * Generate a ProjectRestrictNode to put on top of this node if it's the * source for an insert, and the RCL needs reordering and/or addition of * columns in order to match the target RCL. * * @param target the target node for the insert * @param colMap int array representation of correspondence between * RCLs - colmap[i] = -1 -> missing in current RCL * colmap[i] = j -> targetRCL(i) <-> thisRCL(j+1) * @return a ProjectRestrictNode whos RCL matches the target RCL */ private ResultSetNode generateProjectRestrictForInsert(InsertNode target, int[] colMap) throws StandardException { // our newResultCols are put into the bound form straight away. ResultColumnList newResultCols = (ResultColumnList) getNodeFactory().getNode(C_NodeTypes.RESULT_COLUMN_LIST, getContextManager()); int numTargetColumns = target.resultColumnList.size(); /* Create a massaged version of the source RCL. * (Much simpler to build new list and then assign to source, * rather than massage the source list in place.) */ for (int index = 0; index < numTargetColumns; index++) { ResultColumn newResultColumn; ResultColumn oldResultColumn; ColumnReference newColumnReference; if (colMap[index] != -1) { // getResultColumn uses 1-based positioning, so offset the // colMap entry appropriately oldResultColumn = resultColumns.getResultColumn(colMap[index] + 1); newColumnReference = (ColumnReference) getNodeFactory().getNode(C_NodeTypes.COLUMN_REFERENCE, oldResultColumn.getName(), null, getContextManager()); /* The ColumnReference points to the source of the value */ newColumnReference.setSource(oldResultColumn); // colMap entry is 0-based, columnId is 1-based. newColumnReference.setType(oldResultColumn.getType()); // Source of an insert, so nesting levels must be 0 newColumnReference.setNestingLevel(0); newColumnReference.setSourceLevel(0); // because the insert already copied the target table's // column descriptors into the result, we grab it from there. // alternatively, we could do what the else clause does, // and look it up in the DD again. newResultColumn = (ResultColumn) getNodeFactory().getNode(C_NodeTypes.RESULT_COLUMN, oldResultColumn.getType(), newColumnReference, getContextManager()); } else { newResultColumn = genNewRCForInsert(target.targetTableDescriptor, target.targetVTI, index + 1, target.getDataDictionary()); } newResultCols.addResultColumn(newResultColumn); } /* The generated ProjectRestrictNode now has the ResultColumnList * in the order that the InsertNode expects. * NOTE: This code here is an exception to several "rules": * o This is the only ProjectRestrictNode that is currently * generated outside of preprocess(). * o The UnionNode is the only node which is not at the * top of the query tree which has ColumnReferences under * its ResultColumnList prior to expression push down. */ return (ResultSetNode) getNodeFactory().getNode(C_NodeTypes.PROJECT_RESTRICT_NODE, this, newResultCols, null, null, null, null, null, getContextManager()); } /** * Create a ResultColumn for a column with a generation clause. */ private ResultColumn createGeneratedColumn(TableDescriptor targetTD, ColumnDescriptor colDesc) throws StandardException { ValueNode dummy = (ValueNode) getNodeFactory().getNode(C_NodeTypes.UNTYPED_NULL_CONSTANT_NODE, getContextManager()); ResultColumn newResultColumn = (ResultColumn) getNodeFactory().getNode(C_NodeTypes.RESULT_COLUMN, colDesc.getType(), dummy, getContextManager()); newResultColumn.setColumnDescriptor(targetTD, colDesc); return newResultColumn; } public String printExplainInformation() throws StandardException { return printExplainInformation(getResultSetNumber()); } public String printExplainInformationForActivation() throws StandardException { return WordUtils.wrap(printExplainInformation(", ", getResultSetNumber()), 40, null, false); } }