/*
* 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.resolver;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.teiid.api.exception.query.QueryResolverException;
import org.teiid.api.exception.query.QueryValidatorException;
import org.teiid.core.types.DataTypeManagerService;
import org.teiid.designer.query.IQueryResolver;
import org.teiid.designer.query.metadata.IQueryMetadataInterface;
import org.teiid.designer.query.metadata.IQueryNode;
import org.teiid.designer.query.sql.lang.ICommand;
import org.teiid.designer.runtime.version.spi.ITeiidServerVersion;
import org.teiid.designer.runtime.version.spi.TeiidServerVersion.Version;
import org.teiid.query.mapping.relational.QueryNode;
import org.teiid.query.metadata.TempMetadataAdapter;
import org.teiid.query.metadata.TempMetadataID;
import org.teiid.query.metadata.TempMetadataStore;
import org.teiid.query.parser.QueryParser;
import org.teiid.query.parser.TeiidNodeFactory;
import org.teiid.query.parser.TeiidNodeFactory.ASTNodes;
import org.teiid.query.parser.TeiidParser;
import org.teiid.query.resolver.command.AlterResolver;
import org.teiid.query.resolver.command.DeleteResolver;
import org.teiid.query.resolver.command.DynamicCommandResolver;
import org.teiid.query.resolver.command.ExecResolver;
import org.teiid.query.resolver.command.InsertResolver;
import org.teiid.query.resolver.command.SetQueryResolver;
import org.teiid.query.resolver.command.SimpleQueryResolver;
import org.teiid.query.resolver.command.TempTableResolver;
import org.teiid.query.resolver.command.UpdateProcedureResolver;
import org.teiid.query.resolver.command.UpdateResolver;
import org.teiid.query.resolver.command.XMLQueryResolver;
import org.teiid.query.resolver.util.ResolverUtil;
import org.teiid.query.resolver.util.ResolverVisitor;
import org.teiid.query.sql.lang.Command;
import org.teiid.query.sql.lang.Criteria;
import org.teiid.query.sql.lang.From;
import org.teiid.query.sql.lang.FromClause;
import org.teiid.query.sql.lang.GroupContext;
import org.teiid.query.sql.lang.ProcedureContainer;
import org.teiid.query.sql.lang.Query;
import org.teiid.query.sql.lang.SubqueryContainer;
import org.teiid.query.sql.lang.UnaryFromClause;
import org.teiid.query.sql.navigator.DeepPostOrderNavigator;
import org.teiid.query.sql.proc.CreateUpdateProcedureCommand;
import org.teiid.query.sql.symbol.AliasSymbol;
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.symbol.Reference;
import org.teiid.query.sql.symbol.Symbol;
import org.teiid.query.sql.visitor.ExpressionMappingVisitor;
import org.teiid.query.sql.visitor.ValueIteratorProviderCollectorVisitor;
import org.teiid.query.validator.AbstractValidationVisitor;
import org.teiid.query.validator.UpdateValidator;
import org.teiid.query.validator.UpdateValidator.UpdateInfo;
import org.teiid.query.validator.UpdateValidator.UpdateType;
import org.teiid.query.validator.ValidationVisitor;
import org.teiid.query.validator.Validator;
import org.teiid.query.validator.ValidatorFailure;
import org.teiid.query.validator.ValidatorReport;
import org.teiid.runtime.client.Messages;
import org.teiid.runtime.client.TeiidClientException;
/**
* <P>The QueryResolver is used between Parsing and QueryValidation. The SQL queries,
* inserts, updates and deletes are parsed and converted into objects. The language
* objects have variable names which resolved to fully qualified names using metadata
* information. The resolver is also used in transforming the values in language
* objects to their variable types defined in metadata.
*/
public class QueryResolver implements IQueryResolver<Command, GroupSymbol, Expression> {
private final String BINDING_GROUP = "INPUTS"; //$NON-NLS-1$
private final CommandResolver simpleQueryResolver;
private final CommandResolver setQueryResolver;
private final CommandResolver xmlQueryResolver;
private final ProcedureContainerResolver execResolver;
private final ProcedureContainerResolver insertResolver;
private final ProcedureContainerResolver updateResolver;
private final ProcedureContainerResolver deleteResolver;
private final CommandResolver updateProcedureResolver;
private final CommandResolver dynamicCommandResolver;
private final CommandResolver tempTableResolver;
private final CommandResolver alterResolver;
/*
* The parser that preceded the resolution
*/
private final QueryParser parser;
/**
* @param parser
*/
public QueryResolver(QueryParser parser) {
this.parser = parser;
simpleQueryResolver = new SimpleQueryResolver(this);
setQueryResolver = new SetQueryResolver(this);
xmlQueryResolver = new XMLQueryResolver(this);
execResolver = new ExecResolver(this);
insertResolver = new InsertResolver(this);
updateResolver = new UpdateResolver(this);
deleteResolver = new DeleteResolver(this);
updateProcedureResolver = new UpdateProcedureResolver(this);
dynamicCommandResolver = new DynamicCommandResolver(this);
tempTableResolver = new TempTableResolver(this);
alterResolver = new AlterResolver(this);
}
/**
* @param teiidVersion
*/
public QueryResolver(ITeiidServerVersion teiidVersion) {
this(new QueryParser(teiidVersion));
}
/**
* @return the query parser
*/
public QueryParser getQueryParser() {
return parser;
}
/**
* @return teiid parser
*/
public TeiidParser getTeiidParser() {
return parser.getTeiidParser();
}
/**
* @return parser teiid version
*/
public ITeiidServerVersion getTeiidVersion() {
return getTeiidParser().getVersion();
}
protected boolean isTeiidVersionOrGreater(Version teiidVersion) {
ITeiidServerVersion minVersion = getTeiidVersion().getMinimumVersion();
return minVersion.equals(teiidVersion.get()) || minVersion.isGreaterThan(teiidVersion.get());
}
public Command expandCommand(ProcedureContainer proc, IQueryMetadataInterface metadata) throws Exception {
ProcedureContainerResolver cr = (ProcedureContainerResolver)chooseResolver(proc, metadata);
Command command = cr.expandCommand(proc, metadata);
if (command == null) {
return null;
}
if (command instanceof CreateUpdateProcedureCommand) {
throw new UnsupportedOperationException();
}
resolveCommand(command, proc.getGroup(), proc.getType(), metadata.getDesignTimeMetadata(), false);
return command;
}
/**
* This implements an algorithm to resolve all the symbols created by the
* parser into real metadata IDs
*
* @param command
* Command the SQL command we are running (Select, Update,
* Insert, Delete)
* @param metadata
* IQueryMetadataInterface the metadata
* @return store of metadata ids representing the resolution of all symbols
* @throws Exception
*/
public TempMetadataStore resolveCommand(Command command, IQueryMetadataInterface metadata) throws Exception {
return resolveCommand(command, metadata, true);
}
/**
* Resolve a command in a given type container and type context.
* @param currentCommand
* @param container
* @param type The {@link Command} type
* @param metadata
* @param inferProcedureResultSetColumns if true and the currentCommand is a procedure definition, then resolving will set the getResultSetColumns on the command to what is discoverable in the procedure body.
* @return metadata object store
* @throws Exception
*/
public TempMetadataStore resolveCommand(Command currentCommand, GroupSymbol container, int type, IQueryMetadataInterface metadata, boolean inferProcedureResultSetColumns) throws Exception {
ResolverUtil.resolveGroup(container, metadata);
switch (type) {
case ICommand.TYPE_QUERY:
ResolverUtil.resolveGroup(container, metadata);
IQueryNode queryNode = metadata.getVirtualPlan(container.getMetadataID());
return resolveWithBindingMetadata(currentCommand, metadata, queryNode, false);
case ICommand.TYPE_INSERT:
case ICommand.TYPE_UPDATE:
case ICommand.TYPE_DELETE:
case ICommand.TYPE_STORED_PROCEDURE:
ProcedureContainerResolver.findChildCommandMetadata(this, currentCommand, container, type, metadata, inferProcedureResultSetColumns);
}
return resolveCommand(currentCommand, metadata, false);
}
@Override
public void resolveCommand(Command command, GroupSymbol gSymbol, int teiidCommandType, IQueryMetadataInterface metadata)
throws Exception {
resolveCommand(command, gSymbol, teiidCommandType, metadata, true);
}
@Override
public void postResolveCommand(Command command, GroupSymbol gSymbol, int commandType,
IQueryMetadataInterface metadata, List<Expression> projectedSymbols) {
if (command instanceof CreateUpdateProcedureCommand) {
throw new UnsupportedOperationException();
}
}
/**
* Bindings are a poor mans input parameters. They are represented in legacy metadata
* by ElementSymbols and placed positionally into the command or by alias symbols
* and matched by names. After resolving bindings will be replaced with their
* referenced symbols (input names will not be used) and those symbols will
* be marked as external references.
* @param currentCommand
* @param metadata
* @param queryNode
* @param replaceBindings
* @return metadata object store
* @throws Exception
*/
public TempMetadataStore resolveWithBindingMetadata(Command currentCommand,
IQueryMetadataInterface metadata, IQueryNode queryNode, boolean replaceBindings)
throws Exception {
Map<ElementSymbol, ElementSymbol> symbolMap = null;
TeiidParser teiidParser = parser.getTeiidParser();
if (queryNode.getBindings() != null && queryNode.getBindings().size() > 0) {
symbolMap = new HashMap<ElementSymbol, ElementSymbol>();
// Create ElementSymbols for each InputParameter
final List<ElementSymbol> elements = new ArrayList<ElementSymbol>(queryNode.getBindings().size());
boolean positional = true;
for (Expression ses : parseBindings(queryNode)) {
String name = Symbol.getShortName(ses);
if (ses instanceof AliasSymbol) {
ses = ((AliasSymbol)ses).getSymbol();
positional = false;
}
ElementSymbol elementSymbol = (ElementSymbol)ses;
ResolverVisitor visitor = new ResolverVisitor(getTeiidVersion());
visitor.resolveLanguageObject(elementSymbol, metadata);
elementSymbol.setIsExternalReference(true);
if (!positional) {
ElementSymbol inputSymbol = TeiidNodeFactory.createASTNode(getTeiidVersion(), ASTNodes.ELEMENT_SYMBOL);
inputSymbol.setName("INPUT" + Symbol.SEPARATOR + name); //$NON-NLS-1$
inputSymbol.setType(elementSymbol.getType());
symbolMap.put(inputSymbol, elementSymbol.clone());
ElementSymbol keySymbol = TeiidNodeFactory.createASTNode(getTeiidVersion(), ASTNodes.ELEMENT_SYMBOL);
keySymbol.setName(BINDING_GROUP + Symbol.SEPARATOR + name);
symbolMap.put(keySymbol, elementSymbol.clone());
elementSymbol.setShortName(name);
}
elements.add(elementSymbol);
}
if (positional) {
ExpressionMappingVisitor emv = new ExpressionMappingVisitor(getTeiidVersion(), null) {
@Override
public Expression replaceExpression(Expression element) {
if (!(element instanceof Reference)) {
return element;
}
Reference ref = (Reference)element;
if (!ref.isPositional()) {
return ref;
}
return elements.get(ref.getIndex()).clone();
}
};
DeepPostOrderNavigator.doVisit(currentCommand, emv);
} else {
TempMetadataStore rootExternalStore = new TempMetadataStore();
GroupContext externalGroups = new GroupContext();
ProcedureContainerResolver.addScalarGroup(getTeiidVersion(), "INPUT", rootExternalStore, externalGroups, elements); //$NON-NLS-1$
ProcedureContainerResolver.addScalarGroup(getTeiidVersion(), BINDING_GROUP, rootExternalStore, externalGroups, elements);
setChildMetadata(currentCommand, rootExternalStore, externalGroups);
}
}
TempMetadataStore result = resolveCommand(currentCommand, metadata, false);
if (replaceBindings && symbolMap != null && !symbolMap.isEmpty()) {
ExpressionMappingVisitor emv = new ExpressionMappingVisitor(getTeiidVersion(), symbolMap);
DeepPostOrderNavigator.doVisit(currentCommand, emv);
}
return result;
}
/**
* Bindings are a poor mans input parameters. They are represented in legacy metadata
* by ElementSymbols and placed positionally into the command or by alias symbols
* and matched by names.
* @param planNode
* @return
* @throws Exception
*/
public List<Expression> parseBindings(IQueryNode planNode) throws Exception {
Collection<String> bindingsCol = planNode.getBindings();
if (bindingsCol == null) {
return Collections.emptyList();
}
List<Expression> parsedBindings = new ArrayList<Expression>(bindingsCol.size());
for (Iterator<String> bindings=bindingsCol.iterator(); bindings.hasNext();) {
try {
Expression binding = parser.parseSelectExpression(bindings.next());
parsedBindings.add(binding);
} catch (Exception err) {
throw new TeiidClientException(err, Messages.getString(Messages.TEIID.TEIID30063));
}
}
return parsedBindings;
}
public TempMetadataStore resolveCommand(Command currentCommand, IQueryMetadataInterface metadata, boolean resolveNullLiterals)
throws Exception {
// TODO
// LogManager.logTrace(org.teiid.logging.LogConstants.CTX_QUERY_RESOLVER, new Object[]{"Resolving command", currentCommand}); //$NON-NLS-1$
TempMetadataAdapter resolverMetadata = null;
try {
TempMetadataStore discoveredMetadata = currentCommand.getTemporaryMetadata();
if(discoveredMetadata == null) {
discoveredMetadata = new TempMetadataStore();
currentCommand.setTemporaryMetadata(discoveredMetadata);
}
resolverMetadata = new TempMetadataAdapter(metadata, discoveredMetadata);
// Resolve external groups for command
Collection<GroupSymbol> externalGroups = currentCommand.getAllExternalGroups();
for (GroupSymbol extGroup : externalGroups) {
Object metadataID = extGroup.getMetadataID();
//make sure that the group is resolved and that it is pointing to the appropriate temp group
//TODO: this is mainly for XML resolving since it sends external groups in unresolved
if (metadataID == null || (!(extGroup.getMetadataID() instanceof TempMetadataID) && discoveredMetadata.getTempGroupID(extGroup.getName()) != null)) {
boolean missing = metadataID == null;
metadataID = resolverMetadata.getGroupID(extGroup.getName());
if (missing) {
extGroup.setMetadataID(metadataID);
} else {
//we shouldn't modify the existing, just add a shadow group
GroupSymbol gs = extGroup.clone();
gs.setMetadataID(metadataID);
currentCommand.getExternalGroupContexts().addGroup(gs);
}
}
}
CommandResolver resolver = chooseResolver(currentCommand, resolverMetadata);
// Resolve this command
resolver.resolveCommand(currentCommand, resolverMetadata, resolveNullLiterals);
} catch(Exception e) {
throw new QueryResolverException(e);
}
// Flag that this command has been resolved.
currentCommand.setIsResolved(true);
return resolverMetadata.getMetadataStore();
}
/**
* Method chooseResolver.
* @param command
* @param metadata
* @return CommandResolver
*/
private CommandResolver chooseResolver(Command command, IQueryMetadataInterface metadata)
throws Exception {
switch(command.getType()) {
case ICommand.TYPE_QUERY:
if(command instanceof Query) {
if(isXMLQuery((Query)command, metadata)) {
return xmlQueryResolver;
}
return simpleQueryResolver;
}
return setQueryResolver;
case ICommand.TYPE_INSERT: return insertResolver;
case ICommand.TYPE_UPDATE: return updateResolver;
case ICommand.TYPE_DELETE: return deleteResolver;
case ICommand.TYPE_STORED_PROCEDURE: return execResolver;
case ICommand.TYPE_TRIGGER_ACTION: return updateProcedureResolver;
case ICommand.TYPE_UPDATE_PROCEDURE: return updateProcedureResolver;
// case ICommand.TYPE_BATCHED_UPDATE: return batchedUpdateResolver;
case ICommand.TYPE_DYNAMIC: return dynamicCommandResolver;
case ICommand.TYPE_CREATE: return tempTableResolver;
case ICommand.TYPE_DROP: return tempTableResolver;
case ICommand.TYPE_ALTER_PROC:
case ICommand.TYPE_ALTER_TRIGGER:
case ICommand.TYPE_ALTER_VIEW: return alterResolver;
default:
throw new AssertionError("Unknown command type"); //$NON-NLS-1$
}
}
/**
* Check to verify if the query would return XML results.
* @param query the query to check
* @param metadata IQueryMetadataInterface the metadata
* @return true if query is xml query, false otherwise
* @throws Exception
*/
public boolean isXMLQuery(Query query, IQueryMetadataInterface metadata)
throws Exception {
if (query.getWith() != null) {
return false;
}
// Check first group
From from = query.getFrom();
if(from == null){
//select with no from
return false;
}
if (from.getClauses().size() != 1) {
return false;
}
FromClause clause = from.getClauses().get(0);
if (!(clause instanceof UnaryFromClause)) {
return false;
}
GroupSymbol symbol = ((UnaryFromClause)clause).getGroup();
ResolverUtil.resolveGroup(symbol, metadata);
if (symbol.isProcedure()) {
return false;
}
Object groupID = ((UnaryFromClause)clause).getGroup().getMetadataID();
return metadata.isXMLGroup(groupID);
}
/**
* Resolve just a criteria. The criteria will be modified so nothing is returned.
* @param criteria Criteria to resolve
* @param metadata Metadata implementation
* @throws Exception
*/
public void resolveCriteria(Criteria criteria, IQueryMetadataInterface metadata)
throws Exception {
ResolverVisitor visitor = new ResolverVisitor(getTeiidVersion());
visitor.resolveLanguageObject(criteria, metadata);
}
public void setChildMetadata(Command subCommand, Command parent) {
TempMetadataStore childMetadata = parent.getTemporaryMetadata();
GroupContext parentContext = parent.getExternalGroupContexts();
setChildMetadata(subCommand, childMetadata, parentContext);
}
public void setChildMetadata(Command subCommand, TempMetadataStore parentTempMetadata, GroupContext parentContext) {
TempMetadataStore tempMetadata = subCommand.getTemporaryMetadata();
if(tempMetadata == null) {
subCommand.setTemporaryMetadata(parentTempMetadata.clone());
} else {
tempMetadata.getData().putAll(parentTempMetadata.getData());
}
subCommand.setExternalGroupContexts(parentContext);
}
public Map<ElementSymbol, Expression> getVariableValues(Command command, boolean changingOnly, IQueryMetadataInterface metadata) throws Exception {
CommandResolver resolver = chooseResolver(command, metadata);
if (resolver instanceof VariableResolver) {
return ((VariableResolver)resolver).getVariableValues(command, changingOnly, metadata);
}
return Collections.emptyMap();
}
public void resolveSubqueries(Command command,
TempMetadataAdapter metadata, Collection<GroupSymbol> externalGroups)
throws Exception {
for (SubqueryContainer container : ValueIteratorProviderCollectorVisitor.getValueIteratorProviders(command)) {
setChildMetadata(container.getCommand(), command);
if (externalGroups != null) {
container.getCommand().pushNewResolvingContext(externalGroups);
}
resolveCommand(container.getCommand(), metadata.getMetadata(), false);
}
}
public static void validateWithVisitor(AbstractValidationVisitor visitor, IQueryMetadataInterface metadata, Command command)
throws Exception {
// Validate with visitor
ValidatorReport report = Validator.validate(command, metadata, visitor);
if (report.hasItems()) {
ValidatorFailure firstFailure = report.getItems().iterator().next();
throw new TeiidClientException(firstFailure.getMessage());
}
}
public QueryNode resolveView(GroupSymbol virtualGroup, IQueryNode qnode,
String cacheString, IQueryMetadataInterface qmi) throws Exception {
qmi = qmi.getDesignTimeMetadata();
cacheString = "transformation/" + cacheString; //$NON-NLS-1$
QueryNode cachedNode = (QueryNode)qmi.getFromMetadataCache(virtualGroup.getMetadataID(), cacheString);
if (cachedNode == null) {
Command result = (Command) qnode.getCommand();
List<String> bindings = null;
if (result == null) {
try {
result = getQueryParser().parseCommand(qnode.getQuery());
} catch(Exception e) {
throw new QueryResolverException(Messages.gs(Messages.TEIID.TEIID30065, virtualGroup));
}
bindings = qnode.getBindings();
} else {
result = result.clone();
}
if (bindings != null && !bindings.isEmpty()) {
resolveWithBindingMetadata(result, qmi, qnode, true);
} else {
resolveCommand(result, qmi, false);
}
validateWithVisitor(new ValidationVisitor(getTeiidVersion()), qmi, result);
validateProjectedSymbols(virtualGroup, qmi, result);
cachedNode = new QueryNode(qnode.getQuery());
cachedNode.setCommand(result);
if(isView(virtualGroup, qmi)) {
String updatePlan = qmi.getUpdatePlan(virtualGroup.getMetadataID());
String deletePlan = qmi.getDeletePlan(virtualGroup.getMetadataID());
String insertPlan = qmi.getInsertPlan(virtualGroup.getMetadataID());
//the elements must be against the view and not the alias
if (virtualGroup.getDefinition() != null) {
GroupSymbol group = TeiidNodeFactory.createASTNode(getTeiidVersion(), ASTNodes.GROUP_SYMBOL);
group.setName(virtualGroup.getNonCorrelationName());
group.setMetadataID(virtualGroup.getMetadataID());
virtualGroup = group;
}
List<ElementSymbol> elements = ResolverUtil.resolveElementsInGroup(virtualGroup, qmi);
UpdateValidator validator = new UpdateValidator(qmi, determineType(insertPlan), determineType(updatePlan), determineType(deletePlan));
validator.validate(result, elements);
UpdateInfo info = validator.getUpdateInfo();
cachedNode.setUpdateInfo(info);
}
qmi.addToMetadataCache(virtualGroup.getMetadataID(), cacheString, cachedNode);
}
return cachedNode;
}
public void validateProjectedSymbols(GroupSymbol virtualGroup,
IQueryMetadataInterface qmi, Command result)
throws QueryValidatorException, Exception {
//ensure that null types match the view
List<ElementSymbol> symbols = ResolverUtil.resolveElementsInGroup(virtualGroup, qmi);
List<Expression> projectedSymbols = result.getProjectedSymbols();
validateProjectedSymbols(virtualGroup, symbols, projectedSymbols);
}
public void validateProjectedSymbols(GroupSymbol virtualGroup,
List<? extends Expression> symbols,
List<? extends Expression> projectedSymbols)
throws QueryValidatorException {
if (symbols.size() != projectedSymbols.size()) {
throw new QueryValidatorException(Messages.gs(Messages.TEIID.TEIID30066, virtualGroup, symbols.size(), projectedSymbols.size()));
}
DataTypeManagerService dataTypeManager = DataTypeManagerService.getInstance(getTeiidVersion());
for (int i = 0; i < projectedSymbols.size(); i++) {
Expression projectedSymbol = projectedSymbols.get(i);
ResolverUtil.setTypeIfNull(projectedSymbol, symbols.get(i).getType());
if (projectedSymbol.getType() != symbols.get(i).getType()) {
String symbolTypeName = dataTypeManager.getDataTypeName(symbols.get(i).getType());
String projSymbolTypeName = dataTypeManager.getDataTypeName(projectedSymbol.getType());
throw new QueryValidatorException(Messages.getString(Messages.QueryResolver.wrong_view_symbol_type, virtualGroup, i+1, symbolTypeName, projSymbolTypeName));
}
}
}
public boolean isView(GroupSymbol virtualGroup,
IQueryMetadataInterface qmi) throws Exception {
return !(virtualGroup.getMetadataID() instanceof TempMetadataID) && qmi.isVirtualGroup(virtualGroup.getMetadataID());// && qmi.isVirtualModel(qmi.getModelID(virtualGroup.getMetadataID()));
}
private UpdateType determineType(String plan) {
UpdateType type = UpdateType.INHERENT;
if (plan != null) {
type = UpdateType.INSTEAD_OF;
}
return type;
}
}