/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.sql.optimizer; import com.foundationdb.sql.parser.*; import com.foundationdb.sql.StandardException; import java.util.*; /** * A <code>NodeFactory</code> that knows how to properly clone bindings. */ public class BindingNodeFactory extends NodeFactory { private NodeFactory inner; private Map<QueryTreeNode,QueryTreeNode> nodeMap; private Collection<QueryTreeNode> bindingsToCopy; public static void wrap(SQLParser parser) { NodeFactory nodeFactory = parser.getNodeFactory(); if (!(nodeFactory instanceof BindingNodeFactory)) parser.setNodeFactory(new BindingNodeFactory(nodeFactory)); } /** Construct BindingNodeFactory encapsulating original. */ public BindingNodeFactory(NodeFactory inner) { this.inner = inner; } /** Create node: just passes along to encapsulated. */ public QueryTreeNode getNode(int nodeType, SQLParserContext pc) throws StandardException { return inner.getNode(nodeType, pc); } /** Copy node: pass along, but keep track mappings for those kinds * of nodes that are used in bindings. */ public QueryTreeNode copyNode(QueryTreeNode node, SQLParserContext pc) throws StandardException { boolean newMap = false; if (nodeMap == null) { nodeMap = new HashMap<>(); bindingsToCopy = new ArrayList<>(); newMap = true; } try { QueryTreeNode result = inner.copyNode(node, pc); if ((node instanceof FromTable) || (node instanceof ResultColumn)) nodeMap.put(node, result); return result; } finally { if (newMap) { copyBindings(); nodeMap = null; bindingsToCopy = null; } } } /** Copy user data, which is known to be a appropriate binding object. */ public Object copyUserData(QueryTreeNode node, Object userData) throws StandardException { if ((userData instanceof TableBinding) || (userData instanceof ColumnBinding)) { // Remember to fix later after all copies done. bindingsToCopy.add(node); } // Just return same data for now. return userData; } /** Do the actual binding copies. * Deferred like this so that it does not matter what order the tree * is traversed during the node copy. */ protected void copyBindings() throws StandardException { for (QueryTreeNode node : bindingsToCopy) { Object userData = node.getUserData(); if (userData instanceof TableBinding) userData = new TableBinding((TableBinding)userData); else if (userData instanceof ColumnBinding) { ColumnBinding cb = (ColumnBinding)userData; FromTable oldFromTable = cb.getFromTable(); FromTable newFromTable = (FromTable)nodeMap.get(oldFromTable); if (newFromTable == null) newFromTable = oldFromTable; // Not cloned in this subtree. ResultColumn oldResultColumn = cb.getResultColumn(); if (oldResultColumn == null) { userData = new ColumnBinding(newFromTable, cb.getColumn(), cb.isNullable()); } else { ResultColumn newResultColumn = (ResultColumn)nodeMap.get(oldResultColumn); if (newResultColumn == null) newResultColumn = oldResultColumn; userData = new ColumnBinding(newFromTable, newResultColumn); } } else { continue; } node.setUserData(userData); } } }