/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.query.processor; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.teiid.api.exception.query.ExpressionEvaluationException; import org.teiid.api.exception.query.QueryResolverException; import org.teiid.common.buffer.BlockedException; import org.teiid.common.buffer.TupleSource; import org.teiid.core.TeiidComponentException; import org.teiid.events.EventDistributor; import org.teiid.logging.LogManager; import org.teiid.query.eval.Evaluator; import org.teiid.query.metadata.QueryMetadataInterface; import org.teiid.query.metadata.TempMetadataID; import org.teiid.query.resolver.util.ResolverUtil; import org.teiid.query.sql.lang.*; import org.teiid.query.sql.symbol.ElementSymbol; import org.teiid.query.sql.symbol.Expression; import org.teiid.query.sql.symbol.GroupSymbol; import org.teiid.query.sql.util.SymbolMap; import org.teiid.query.sql.visitor.ReferenceCollectorVisitor; import org.teiid.query.util.CommandContext; public class FakeDataManager implements ProcessorDataManager { static class TupleInfo { List<ElementSymbol> elements; List<?>[] data; public TupleInfo(List<ElementSymbol> elements, List<?>[] data) { this.elements = elements; this.data = data; } } private Map<String, TupleInfo> tuples = new HashMap<String, TupleInfo>(); private Map<String, List<List<?>>[]> procTuples = new HashMap<String, List<List<?>>[]>(); private static final String LOG_CONTEXT = "FAKE_DATA_MANAGER"; //$NON-NLS-1$ //used to test blocked exception. If true, //the first time nextTuple is called on FakeTupleSource, //it will throws BlockedExceptiom private boolean blockOnce; // ---- Cached code table stuff ---- // upper table name + upper key col name + upper ret col name -> map of values private Map<String, Map> codeTableValues = new HashMap<String, Map>(); // throw Blocked on first request private boolean throwBlocked = false; // upper table name + upper key col name + upper ret col name -> flag of whether this table has blocked yet private Map<String, Boolean> blockedState = new HashMap<String, Boolean>(); // Track history to verify it later private List<String> queries = new ArrayList<String>(); private boolean recordingCommands = true; /** * Return string form of all queries run against this FDM * @return List<String> recorded commands */ public List<String> getQueries() { return this.queries; } /** * Clears the list of recorded commands and returns a copy * @return a copy of the recorded commands prior to clearing the list */ public List<String> clearQueries() { List<String> rc = new ArrayList<String>(this.getQueries()); this.queries.clear(); return rc; } public void registerProcTuples(String proc, List[] data) { procTuples.put(proc, data); } public TupleSource registerRequest(CommandContext context, Command command, String modelName, RegisterRequestParameter parameterObject) throws TeiidComponentException { LogManager.logTrace(LOG_CONTEXT, new Object[]{"Register Request:", command, ",processorID:", context.getRequestId(), ",model name:", modelName,",TupleSourceID nodeID:",new Integer(parameterObject.nodeID)}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ if (this.recordingCommands) { if (! (command instanceof BatchedUpdateCommand) ) { this.queries.add(command.toString()); } } if (ReferenceCollectorVisitor.getReferences(command).size() > 0) { throw new IllegalArgumentException("Found references in the command registered with the DataManager."); //$NON-NLS-1$ } // Get group ID from atomic command GroupSymbol group = null; if(command instanceof Query){ group = getQueryGroup((Query)command); }else if(command instanceof SetQuery) { SetQuery union = (SetQuery) command; group = getQueryGroup(union.getProjectedQuery()); } else if (command instanceof StoredProcedure) { Object id = ((StoredProcedure) command).getProcedureID(); List<List<?>>[] data = procTuples.get(id); if (data == null) { throw new AssertionError("Undefined results for " + command); //$NON-NLS-1$ } FakeTupleSource ts= new FakeTupleSource(command.getProjectedSymbols(), data); if(this.blockOnce){ ts.setBlockOnce(); } return ts; } else if (command instanceof ProcedureContainer) { group = ((ProcedureContainer) command).getGroup(); } else if ( command instanceof BatchedUpdateCommand ) { BatchedUpdateCommand buc = (BatchedUpdateCommand)command; if ( buc.getUpdateCommands().get(0) instanceof Update ) { group = ((Update)buc.getUpdateCommands().get(0)).getGroup(); } if (this.recordingCommands) { for ( Iterator<Command> it = ((BatchedUpdateCommand) command).getUpdateCommands().iterator(); it.hasNext(); ) { this.queries.add(it.next().toString()); } } } TupleInfo tupleInfo = tuples.get(group.getNonCorrelationName().toUpperCase()); List<? extends Expression> elements = tupleInfo.elements; List<?>[] data = tupleInfo.data; List<Expression> projectedSymbols = command.getProjectedSymbols(); int[] columnMap = getColumnMap(tupleInfo.elements, projectedSymbols); /* * updateCommands is used to hold a list of commands that * either came from a BatchedUpdateCommand or a signle * command from an Update command. */ List<Command> updateCommands = new ArrayList<Command>(); // Apply query criteria to tuples if(command instanceof Query){ Query query = (Query)command; if(query.getCriteria() != null) { // Build lookupMap from BOTH all the elements and the projected symbols - both may be needed here Map lookupMap = new HashMap(); for(int i=0; i<elements.size(); i++) { Expression element = elements.get(i); mapElementToIndex(lookupMap, element, i, group); } for(int i=0; i<projectedSymbols.size(); i++) { Expression element = projectedSymbols.get(i); mapElementToIndex(lookupMap, element, columnMap[i], group); } List filteredTuples = new ArrayList(); for(int i=0; i<data.length; i++) { try { if(new Evaluator(lookupMap, null, null).evaluate(query.getCriteria(), data[i])) { filteredTuples.add(data[i]); } } catch(ExpressionEvaluationException e) { throw new TeiidComponentException(e, e.getMessage()); } } data = new List[filteredTuples.size()]; filteredTuples.toArray(data); } } else if ( command instanceof Insert || command instanceof Update || command instanceof Delete) { // add single update command to a list to be executed updateCommands.add(command); } else if ( command instanceof BatchedUpdateCommand ) { // add all update commands to a list to be executed updateCommands.addAll(((BatchedUpdateCommand) command).getUpdateCommands()); } // if we had update commands added to the list, execute them now if ( updateCommands.size() > 0 ) { List<List<Integer>> filteredTuples = new ArrayList<List<Integer>>(); for ( int c = 0; c < updateCommands.size(); c++ ) { Command cmd = updateCommands.get(c); if (cmd instanceof FilteredCommand) { FilteredCommand update = (FilteredCommand)cmd; if ( update.getCriteria() != null ) { // Build lookupMap from BOTH all the elements and the projected symbols - both may be needed here Map<Object, Integer> lookupMap = new HashMap<Object, Integer>(); for(int i=0; i<elements.size(); i++) { Expression element = elements.get(i); mapElementToIndex(lookupMap, element, new Integer(i), group); } for(int i=0; i<projectedSymbols.size(); i++) { Expression element = projectedSymbols.get(i); mapElementToIndex(lookupMap, element, new Integer(columnMap[i]), group); } int updated = 0; for(int i=0; i<data.length; i++) { try { if(new Evaluator(lookupMap, null, null).evaluate(update.getCriteria(), data[i])) { updated++; } } catch(ExpressionEvaluationException e) { throw new TeiidComponentException(e, e.getMessage()); } } List<Integer> updateTuple = new ArrayList<Integer>(1); updateTuple.add( new Integer(updated) ); filteredTuples.add(updateTuple); } } else { filteredTuples.add(Arrays.asList(1)); //TODO: check for bulk } } data = new List[filteredTuples.size()]; filteredTuples.toArray(data); elements = Command.getUpdateCommandSymbol(); columnMap[0] = 0; } FakeTupleSource ts= new FakeTupleSource(elements, data, projectedSymbols, columnMap); if(this.blockOnce){ ts.setBlockOnce(); } return ts; } private GroupSymbol getQueryGroup(Query query) throws TeiidComponentException { GroupSymbol group; From from = query.getFrom(); List groups = from.getGroups(); if(groups.size() != 1) { throw new TeiidComponentException("Cannot build fake tuple source for command: " + query); //$NON-NLS-1$ } group = (GroupSymbol) groups.get(0); Iterator projSymbols = query.getSelect().getProjectedSymbols().iterator(); while (projSymbols.hasNext()) { Object symbol = projSymbols.next(); if (symbol instanceof ElementSymbol){ ElementSymbol elementSymbol = (ElementSymbol)symbol; GroupSymbol g = elementSymbol.getGroupSymbol(); if (!g.equals(group)){ throw new TeiidComponentException("Illegal symbol " + elementSymbol + " in SELECT of command: " + query); //$NON-NLS-1$ //$NON-NLS-2$ } if (elementSymbol.getMetadataID() == null){ throw new TeiidComponentException("Illegal null metadata ID in ElementSymbol " + elementSymbol + " in SELECT of command: " + query); //$NON-NLS-1$ //$NON-NLS-2$ } else if (elementSymbol.getMetadataID() instanceof TempMetadataID){ throw new TeiidComponentException("Illegal TempMetadataID in ElementSymbol " + elementSymbol + " in SELECT of command: " + query); //$NON-NLS-1$ //$NON-NLS-2$ } } } return group; } /** * @param lookupMap * @param element * @param integer * @param group */ private void mapElementToIndex(Map lookupMap, Expression element, Integer index, GroupSymbol group) { ElementSymbol elementSymbol = (ElementSymbol)SymbolMap.getExpression(element); if (group.getDefinition() != null){ String groupAlias = group.getName(); elementSymbol = elementSymbol.clone(); elementSymbol.getGroupSymbol().setName(groupAlias); } lookupMap.put(elementSymbol, index); } // columnMap[expectedElementIndex] = allElementIndex private int[] getColumnMap(List<ElementSymbol> allElements, List<? extends Expression> expectedElements) { int[] map = new int[expectedElements.size()]; for(int i=0; i<expectedElements.size(); i++) { Expression symbol = SymbolMap.getExpression(expectedElements.get(i)); boolean foundMatch = false; if (symbol instanceof ElementSymbol) { // Find matching short name in all elements for(int j=0; j<allElements.size(); j++) { ElementSymbol es = allElements.get(j); if(es.getShortName().equalsIgnoreCase(((ElementSymbol)symbol).getShortName())) { map[i] = j; foundMatch = true; break; } } } if(! foundMatch) { map[i] = -1; } } return map; } public void setThrowBlocked(boolean throwBlocked) { this.throwBlocked = throwBlocked; } public void defineCodeTable(String tableName, String keyCol, String retCol, Map values) { String key = tableName.toUpperCase() + keyCol.toUpperCase() + retCol.toUpperCase(); this.codeTableValues.put(key, values); this.blockedState.put(key, Boolean.FALSE); } public Object lookupCodeValue( CommandContext context, String codeTableName, String returnElementName, String keyElementName, Object keyValue) throws BlockedException, TeiidComponentException { String tableKey = codeTableName.toUpperCase() + keyElementName.toUpperCase() + returnElementName.toUpperCase(); if(! codeTableValues.containsKey(tableKey)) { throw new TeiidComponentException("Unknown code table: " + codeTableName); //$NON-NLS-1$ } if(throwBlocked) { if(blockedState.get(tableKey).equals(Boolean.FALSE)) { blockedState.put(tableKey, Boolean.TRUE); throw BlockedException.INSTANCE; } } Map values = codeTableValues.get(tableKey); return values.get(keyValue); } public void setBlockOnce() { blockOnce = true; } /** * Are commands/queries that are registered with the data manager being * recorded? * <p> * Recorded commands can be retrieved by {@link #getQueries()} * * @return whether or not commands should be recorded */ public boolean isRecordingCommands() { return recordingCommands; } /** * Indicate whether or not commands/queries registered with the data * manager are to be recorded in {@link #queries}. * <p> * Recorded commands can be retrieved by {@link #getQueries()} * * @param shouldRecord should commands be recorded? */ public void setRecordingCommands(boolean shouldRecord) { this.recordingCommands = shouldRecord; } public void registerTuples(QueryMetadataInterface metadata, String groupName, List<?>[] data) throws QueryResolverException, TeiidComponentException { GroupSymbol group = new GroupSymbol(groupName); ResolverUtil.resolveGroup(group, metadata); List<ElementSymbol> elementSymbols = ResolverUtil.resolveElementsInGroup(group, metadata); tuples.put(group.getName().toUpperCase(), new TupleInfo(elementSymbols, data)); } @Override public EventDistributor getEventDistributor() { // TODO Auto-generated method stub return null; } }