/*
* 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.rewriter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.*;
import org.teiid.api.exception.query.ExpressionEvaluationException;
import org.teiid.api.exception.query.FunctionExecutionException;
import org.teiid.api.exception.query.QueryMetadataException;
import org.teiid.api.exception.query.QueryResolverException;
import org.teiid.api.exception.query.QueryValidatorException;
import org.teiid.common.buffer.BlockedException;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.core.TeiidRuntimeException;
import org.teiid.core.types.DataTypeManager;
import org.teiid.core.types.DataTypeManager.DefaultDataClasses;
import org.teiid.core.types.Transform;
import org.teiid.core.util.Assertion;
import org.teiid.core.util.StringUtil;
import org.teiid.core.util.TimestampWithTimezone;
import org.teiid.language.Like.MatchMode;
import org.teiid.language.SQLConstants;
import org.teiid.query.QueryPlugin;
import org.teiid.query.eval.Evaluator;
import org.teiid.query.function.FunctionDescriptor;
import org.teiid.query.function.FunctionLibrary;
import org.teiid.query.function.FunctionMethods;
import org.teiid.query.metadata.MaterializationMetadataRepository;
import org.teiid.query.metadata.QueryMetadataInterface;
import org.teiid.query.metadata.TempMetadataAdapter;
import org.teiid.query.metadata.TempMetadataID;
import org.teiid.query.metadata.TempMetadataStore;
import org.teiid.query.optimizer.relational.rules.NewCalculateCostUtil;
import org.teiid.query.optimizer.relational.rules.RuleMergeCriteria;
import org.teiid.query.optimizer.relational.rules.RuleMergeCriteria.PlannedResult;
import org.teiid.query.optimizer.relational.rules.RulePlaceAccess;
import org.teiid.query.processor.relational.RelationalNodeUtil;
import org.teiid.query.resolver.ProcedureContainerResolver;
import org.teiid.query.resolver.QueryResolver;
import org.teiid.query.resolver.util.ResolverUtil;
import org.teiid.query.resolver.util.ResolverVisitor;
import org.teiid.query.sql.LanguageObject;
import org.teiid.query.sql.LanguageObject.Util;
import org.teiid.query.sql.LanguageVisitor;
import org.teiid.query.sql.ProcedureReservedWords;
import org.teiid.query.sql.lang.*;
import org.teiid.query.sql.lang.PredicateCriteria.Negatable;
import org.teiid.query.sql.navigator.DeepPostOrderNavigator;
import org.teiid.query.sql.navigator.DeepPreOrderNavigator;
import org.teiid.query.sql.navigator.PostOrderNavigator;
import org.teiid.query.sql.navigator.PreOrPostOrderNavigator;
import org.teiid.query.sql.proc.*;
import org.teiid.query.sql.symbol.*;
import org.teiid.query.sql.symbol.AggregateSymbol.Type;
import org.teiid.query.sql.util.SymbolMap;
import org.teiid.query.sql.visitor.AggregateSymbolCollectorVisitor;
import org.teiid.query.sql.visitor.CorrelatedReferenceCollectorVisitor;
import org.teiid.query.sql.visitor.ElementCollectorVisitor;
import org.teiid.query.sql.visitor.EvaluatableVisitor;
import org.teiid.query.sql.visitor.EvaluatableVisitor.EvaluationLevel;
import org.teiid.query.sql.visitor.ExpressionMappingVisitor;
import org.teiid.query.sql.visitor.FunctionCollectorVisitor;
import org.teiid.query.sql.visitor.GroupCollectorVisitor;
import org.teiid.query.sql.visitor.ValueIteratorProviderCollectorVisitor;
import org.teiid.query.util.CommandContext;
import org.teiid.query.validator.UpdateValidator.UpdateInfo;
import org.teiid.query.validator.UpdateValidator.UpdateMapping;
import org.teiid.query.validator.ValidationVisitor;
import org.teiid.translator.SourceSystemFunctions;
/**
* Rewrites commands and command fragments to a form that is better for planning and execution. There is a current limitation that
* command objects themselves cannot change type, since the same object is always used.
*/
public class QueryRewriter {
private static final String WRITE_THROUGH = "write-through"; //$NON-NLS-1$
private static final Constant ZERO_CONSTANT = new Constant(0, DataTypeManager.DefaultDataClasses.INTEGER);
public static final CompareCriteria TRUE_CRITERIA = new CompareCriteria(new Constant(1, DataTypeManager.DefaultDataClasses.INTEGER), CompareCriteria.EQ, new Constant(1, DataTypeManager.DefaultDataClasses.INTEGER));
public static final CompareCriteria FALSE_CRITERIA = new CompareCriteria(new Constant(1, DataTypeManager.DefaultDataClasses.INTEGER), CompareCriteria.EQ, ZERO_CONSTANT) {
public void setOptional(Boolean isOptional) {};
};
public static final CompareCriteria UNKNOWN_CRITERIA = new CompareCriteria(new Constant(null, DataTypeManager.DefaultDataClasses.STRING), CompareCriteria.NE, new Constant(null, DataTypeManager.DefaultDataClasses.STRING)) {
public void setOptional(Boolean isOptional) {};
};
private static final Map<String, String> ALIASED_FUNCTIONS = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
private static final Set<String> PARSE_FORMAT_TYPES = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
static {
ALIASED_FUNCTIONS.put("lower", SourceSystemFunctions.LCASE); //$NON-NLS-1$
ALIASED_FUNCTIONS.put("upper", SourceSystemFunctions.UCASE); //$NON-NLS-1$
ALIASED_FUNCTIONS.put("cast", SourceSystemFunctions.CONVERT); //$NON-NLS-1$
ALIASED_FUNCTIONS.put("nvl", SourceSystemFunctions.IFNULL); //$NON-NLS-1$
ALIASED_FUNCTIONS.put("||", SourceSystemFunctions.CONCAT); //$NON-NLS-1$
ALIASED_FUNCTIONS.put("chr", SourceSystemFunctions.CHAR); //$NON-NLS-1$
ALIASED_FUNCTIONS.put("substr", SourceSystemFunctions.SUBSTRING); //$NON-NLS-1$
ALIASED_FUNCTIONS.put("st_geomfrombinary", SourceSystemFunctions.ST_GEOMFROMWKB); //$NON-NLS-1$
PARSE_FORMAT_TYPES.addAll( Arrays.asList(DataTypeManager.DefaultDataTypes.TIME,
DataTypeManager.DefaultDataTypes.DATE, DataTypeManager.DefaultDataTypes.TIMESTAMP, DataTypeManager.DefaultDataTypes.BIG_DECIMAL,
DataTypeManager.DefaultDataTypes.BIG_INTEGER, DataTypeManager.DefaultDataTypes.INTEGER, DataTypeManager.DefaultDataTypes.LONG,
DataTypeManager.DefaultDataTypes.FLOAT, DataTypeManager.DefaultDataTypes.DOUBLE));
}
// Constants used in simplifying mathematical criteria
private final static Integer INTEGER_ZERO = new Integer(0);
private final static Double DOUBLE_ZERO = new Double(0);
private final static Float FLOAT_ZERO = new Float(0);
private final static Long LONG_ZERO = new Long(0);
private final static BigInteger BIG_INTEGER_ZERO = new BigInteger("0"); //$NON-NLS-1$
private final static BigDecimal BIG_DECIMAL_ZERO = new BigDecimal("0"); //$NON-NLS-1$
private final static Short SHORT_ZERO = new Short((short)0);
private final static Byte BYTE_ZERO = new Byte((byte)0);
private boolean rewriteAggs = true;
private boolean preserveUnknown;
private QueryMetadataInterface metadata;
private CommandContext context;
private boolean rewriteSubcommands;
private boolean processing;
private Evaluator evaluator;
private Map<ElementSymbol, Expression> variables; //constant propagation
private QueryRewriter(QueryMetadataInterface metadata,
CommandContext context) {
this.metadata = metadata;
this.context = context;
this.evaluator = new Evaluator(Collections.emptyMap(), null, context);
}
public static Command evaluateAndRewrite(Command command, Evaluator eval, CommandContext context, QueryMetadataInterface metadata) throws TeiidProcessingException, TeiidComponentException {
QueryRewriter queryRewriter = new QueryRewriter(metadata, context);
queryRewriter.evaluator = eval;
queryRewriter.rewriteSubcommands = true;
queryRewriter.processing = true;
return queryRewriter.rewriteCommand(command, false);
}
public static Command rewrite(Command command, QueryMetadataInterface metadata, CommandContext context, Map<ElementSymbol, Expression> variableValues) throws TeiidComponentException, TeiidProcessingException{
QueryRewriter rewriter = new QueryRewriter(metadata, context);
rewriter.rewriteSubcommands = true;
rewriter.variables = variableValues;
return rewriter.rewriteCommand(command, false);
}
public static Command rewrite(Command command, QueryMetadataInterface metadata, CommandContext context) throws TeiidComponentException, TeiidProcessingException{
return rewrite(command, metadata, context, null);
}
/**
* Rewrites the command and all of its subcommands (both embedded and non-embedded)
*
* @param command
* @param removeOrderBy
* @return
* @throws QueryValidatorException
*/
private Command rewriteCommand(Command command, boolean removeOrderBy) throws TeiidComponentException, TeiidProcessingException{
boolean oldRewriteAggs = rewriteAggs;
QueryMetadataInterface oldMetadata = metadata;
TempMetadataStore tempMetadata = command.getTemporaryMetadata();
if(tempMetadata != null) {
metadata = new TempMetadataAdapter(metadata, tempMetadata);
}
switch(command.getType()) {
case Command.TYPE_QUERY:
QueryCommand queryCommand = (QueryCommand)command;
if (removeOrderBy && queryCommand.getLimit() == null) {
queryCommand.setOrderBy(null);
}
List<WithQueryCommand> withList = queryCommand.getWith();
if (withList != null) {
queryCommand.setWith(null);
List<UnaryFromClause> clauses = getUnaryFromClauses(queryCommand);
queryCommand.setWith(withList);
outer: for (int i = withList.size() - 1; i >= 0; i--) {
WithQueryCommand withQueryCommand = withList.get(i);
if (withQueryCommand.getColumns() == null) {
List<ElementSymbol> columns = ResolverUtil.resolveElementsInGroup(withQueryCommand.getGroupSymbol(), metadata);
withQueryCommand.setColumns(LanguageObject.Util.deepClone(columns, ElementSymbol.class));
}
Collection<UnaryFromClause> all = new ArrayList<UnaryFromClause>(clauses);
List<UnaryFromClause> current = getUnaryFromClauses(withQueryCommand.getCommand());
clauses.addAll(current);
rewriteSubqueryContainer(withQueryCommand, true);
//can't inline with a hint or once it's planned
if (withQueryCommand.isNoInline() || withQueryCommand.getCommand().getProcessorPlan() != null || processing) {
//TODO: in the processing case we may want to remove unneeded cte declarations, rather than
//pushing them down
continue;
}
boolean removeOnly = false;
//check for scalar with clauses
boolean replaceScalar = replaceScalar(withQueryCommand);
if (!replaceScalar) {
int referenceCount = 0;
for (UnaryFromClause ufc : all) {
if (ufc.getGroup().getMetadataID() != withQueryCommand.getGroupSymbol().getMetadataID()) {
continue;
}
referenceCount++;
if (referenceCount > 1) {
continue outer; //referenced in more than 1 location
}
}
if (referenceCount == 0) {
removeOnly = true;
} else if (withQueryCommand.isRecursive()) {
continue; //can't inline if recursive
}
}
withList.remove(i);
if (withList.isEmpty()) {
queryCommand.setWith(null);
}
if (removeOnly) {
clauses = clauses.subList(0, clauses.size() - current.size());
continue;
}
for (UnaryFromClause clause : all) {
//we match on equality as the name can be redefined
if (clause.getGroup().getMetadataID() != withQueryCommand.getGroupSymbol().getMetadataID()) {
continue;
}
if (!replaceScalar) {
//use the original since we need to keep the references
//to nested unaryfromclause instances
clause.setExpandedCommand(withQueryCommand.getCommand());
break;
}
clause.setExpandedCommand((Command) withQueryCommand.getCommand().clone());
}
}
}
if(command instanceof Query) {
command = rewriteQuery((Query) command);
}else {
command = rewriteSetQuery((SetQuery) command);
}
break;
case Command.TYPE_STORED_PROCEDURE:
command = rewriteExec((StoredProcedure) command);
break;
case Command.TYPE_INSERT:
command = rewriteInsert((Insert) command);
break;
case Command.TYPE_UPDATE:
command = rewriteUpdate((Update) command);
break;
case Command.TYPE_DELETE:
command = rewriteDelete((Delete) command);
break;
case Command.TYPE_UPDATE_PROCEDURE:
command = rewriteUpdateProcedure((CreateProcedureCommand) command);
break;
case Command.TYPE_BATCHED_UPDATE:
List subCommands = ((BatchedUpdateCommand)command).getUpdateCommands();
for (int i = 0; i < subCommands.size(); i++) {
Command subCommand = (Command)subCommands.get(i);
subCommand = rewriteCommand(subCommand, false);
subCommands.set(i, subCommand);
}
break;
case Command.TYPE_TRIGGER_ACTION:
TriggerAction ta = (TriggerAction)command;
ta.setBlock(rewriteBlock(ta.getBlock()));
break;
}
this.rewriteAggs = oldRewriteAggs;
this.metadata = oldMetadata;
return command;
}
private boolean replaceScalar(WithQueryCommand withQueryCommand) {
if (!GroupCollectorVisitor.getGroups(withQueryCommand.getCommand(), false).isEmpty()) {
return false;
}
//if deterministic, just inline
return !FunctionCollectorVisitor.isNonDeterministic(withQueryCommand.getCommand());
}
private List<UnaryFromClause> getUnaryFromClauses(QueryCommand queryCommand) {
final List<UnaryFromClause> clauses = new ArrayList<UnaryFromClause>();
LanguageVisitor visitor = new LanguageVisitor() {
public void visit(UnaryFromClause obj) {
clauses.add(obj);
}
};
DeepPreOrderNavigator.doVisit(queryCommand, visitor);
return clauses;
}
private Command rewriteUpdateProcedure(CreateProcedureCommand command) throws TeiidComponentException {
Block block = rewriteBlock(command.getBlock());
command.setBlock(block);
return command;
}
private Block rewriteBlock(Block block) throws TeiidComponentException {
List<Statement> statements = block.getStatements();
List<Statement> newStmts = rewriteStatements(statements);
block.setStatements(newStmts);
if (block.getExceptionStatements() != null) {
block.setExceptionStatements(rewriteStatements(block.getExceptionStatements()));
}
return block;
}
private List<Statement> rewriteStatements(List<Statement> statements) throws TeiidComponentException {
Iterator<Statement> stmtIter = statements.iterator();
List<Statement> newStmts = new ArrayList<Statement>(statements.size());
// plan each statement in the block
while(stmtIter.hasNext()) {
Statement stmnt = stmtIter.next();
try {
rewriteStatement(stmnt, newStmts);
} catch (TeiidProcessingException e) {
/*
* defer the processing of the exception until runtime as there may be an exception handler
*/
RaiseStatement raise = new RaiseStatement(new Constant(e));
newStmts.add(raise);
break;
}
}
return newStmts;
}
private void rewriteStatement(Statement statement, List<Statement> newStmts)
throws TeiidComponentException, TeiidProcessingException{
// evaluate the HAS Criteria on the procedure and rewrite
int stmtType = statement.getType();
switch(stmtType) {
case Statement.TYPE_IF:
IfStatement ifStmt = (IfStatement) statement;
Criteria ifCrit = ifStmt.getCondition();
Criteria evalCrit = rewriteCriteria(ifCrit);
ifStmt.setCondition(evalCrit);
if(evalCrit.equals(TRUE_CRITERIA)) {
Block ifblock = rewriteBlock(ifStmt.getIfBlock());
if (ifblock.isAtomic()) {
newStmts.add(ifblock);
} else {
newStmts.addAll(ifblock.getStatements());
}
return;
} else if(evalCrit.equals(FALSE_CRITERIA) || evalCrit.equals(UNKNOWN_CRITERIA)) {
if(ifStmt.hasElseBlock()) {
Block elseBlock = rewriteBlock(ifStmt.getElseBlock());
if (elseBlock.isAtomic()) {
newStmts.add(elseBlock);
} else {
newStmts.addAll(elseBlock.getStatements());
}
return;
}
return;
} else {
Block ifblock = rewriteBlock(ifStmt.getIfBlock());
ifStmt.setIfBlock(ifblock);
if(ifStmt.hasElseBlock()) {
Block elseBlock = rewriteBlock(ifStmt.getElseBlock());
ifStmt.setElseBlock(elseBlock);
}
}
break;
case Statement.TYPE_ERROR:
case Statement.TYPE_DECLARE:
case Statement.TYPE_ASSIGNMENT:
case Statement.TYPE_RETURN:
ExpressionStatement exprStmt = (ExpressionStatement) statement;
// replace variables to references, these references are later
// replaced in the processor with variable values
Expression expr = exprStmt.getExpression();
if (expr != null) {
boolean preserveUnknownOld = preserveUnknown;
preserveUnknown = true;
expr = rewriteExpressionDirect(expr);
preserveUnknown = preserveUnknownOld;
exprStmt.setExpression(expr);
}
break;
case Statement.TYPE_COMMAND:
CommandStatement cmdStmt = (CommandStatement) statement;
rewriteSubqueryContainer(cmdStmt, false);
if(cmdStmt.getCommand().getType() == Command.TYPE_UPDATE) {
Update update = (Update)cmdStmt.getCommand();
if (update.getChangeList().isEmpty()) {
return;
}
}
break;
case Statement.TYPE_LOOP:
LoopStatement loop = (LoopStatement)statement;
rewriteSubqueryContainer(loop, false);
rewriteBlock(loop.getBlock());
if (loop.getBlock().getStatements().isEmpty()) {
return;
}
break;
case Statement.TYPE_WHILE:
WhileStatement whileStatement = (WhileStatement) statement;
Criteria crit = whileStatement.getCondition();
crit = rewriteCriteria(crit);
whileStatement.setCondition(crit);
if(crit.equals(FALSE_CRITERIA) || crit.equals(UNKNOWN_CRITERIA)) {
return;
}
whileStatement.setBlock(rewriteBlock(whileStatement.getBlock()));
if (whileStatement.getBlock().getStatements().isEmpty()) {
return;
}
break;
case Statement.TYPE_COMPOUND:
statement = rewriteBlock((Block) statement);
break;
}
newStmts.add(statement);
}
/**
* @param removeOrderBy
* @param assStmt
* @throws QueryValidatorException
*/
private void rewriteSubqueryContainer(SubqueryContainer container, boolean removeOrderBy) throws TeiidComponentException, TeiidProcessingException{
if (rewriteSubcommands && container.getCommand() != null && (container.getCommand().getProcessorPlan() == null || processing)) {
container.setCommand(rewriteCommand(container.getCommand(), removeOrderBy));
}
}
private Command rewriteQuery(Query query)
throws TeiidComponentException, TeiidProcessingException{
// Rewrite from clause
From from = query.getFrom();
if(from != null){
List<FromClause> clauses = new ArrayList<FromClause>(from.getClauses().size());
Iterator<FromClause> clauseIter = from.getClauses().iterator();
while(clauseIter.hasNext()) {
clauses.add( rewriteFromClause(query, clauseIter.next()) );
}
from.setClauses(clauses);
} else {
query.setOrderBy(null);
}
// Rewrite criteria
Criteria crit = query.getCriteria();
if(crit != null) {
if (query.getIsXML()) {
//only rewrite expressions, removing whole predicates could change the nature of the query,
//because we aren't considering the xml pseudo-functions context, row limit, etc.
rewriteExpressions(crit);
} else {
boolean preserveUnknownOld = preserveUnknown;
preserveUnknown = false;
crit = rewriteCriteria(crit);
preserveUnknown = preserveUnknownOld;
}
if(crit == TRUE_CRITERIA) {
query.setCriteria(null);
} else if (crit == UNKNOWN_CRITERIA) {
query.setCriteria(FALSE_CRITERIA);
} else {
query.setCriteria(crit);
}
}
if (from != null && !query.getIsXML()) {
rewriteSubqueriesAsJoins(query);
}
query = rewriteGroupBy(query);
// Rewrite having
Criteria having = query.getHaving();
if(having != null) {
boolean preserveUnknownOld = preserveUnknown;
preserveUnknown = false;
crit = rewriteCriteria(having);
preserveUnknown = preserveUnknownOld;
if(crit == TRUE_CRITERIA) {
query.setHaving(null);
} else {
query.setHaving(crit);
}
}
if (!query.getIsXML()) {
//remove multiple element symbols
boolean hasMes = false;
for (Expression ex : query.getSelect().getSymbols()) {
if (ex instanceof MultipleElementSymbol) {
hasMes = true;
}
}
if (hasMes) {
query.getSelect().setSymbols(query.getSelect().getProjectedSymbols());
}
}
boolean preserveUnknownOld = preserveUnknown;
preserveUnknown = true;
rewriteExpressions(query.getSelect());
if (from != null && !query.getIsXML()) {
List<Expression> symbols = query.getSelect().getSymbols();
RuleMergeCriteria rmc = new RuleMergeCriteria(null, null, null, this.context, this.metadata);
TreeSet<String> names = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
List<GroupSymbol> groups = query.getFrom().getGroups();
for (GroupSymbol gs : groups) {
names.add(gs.getName());
}
PlannedResult plannedResult = new PlannedResult();
for (int i = 0; i < symbols.size(); i++) {
Expression symbol = symbols.get(i);
plannedResult.reset();
rmc.findSubquery(SymbolMap.getExpression(symbol), context!=null?context.getOptions().isSubqueryUnnestDefault():false, plannedResult);
if (plannedResult.query == null || plannedResult.query.getProcessorPlan() != null
|| plannedResult.query.getFrom() == null) {
continue;
}
determineCorrelatedReferences(groups, plannedResult);
boolean requiresDistinct = requiresDistinctRows(query);
if (!rmc.planQuery(groups, requiresDistinct, plannedResult)) {
continue;
}
Query q = convertToJoin(plannedResult, names, query, true);
symbols.set(i, new AliasSymbol(ExpressionSymbol.getName(symbol), (Expression) q.getProjectedSymbols().get(0).clone()));
}
}
if (!query.getIsXML()) {
query = (Query)rewriteOrderBy(query);
}
preserveUnknown = preserveUnknownOld;
if (query.getLimit() != null) {
query.setLimit(rewriteLimitClause(query.getLimit()));
}
if (query.getInto() != null) {
return rewriteSelectInto(query);
}
return query;
}
private void rewriteSubqueriesAsJoins(Query query)
throws TeiidComponentException, QueryMetadataException,
QueryResolverException {
if (query.getCriteria() == null) {
return;
}
RuleMergeCriteria rmc = new RuleMergeCriteria(null, null, null, this.context, this.metadata);
List<Criteria> current = Criteria.separateCriteriaByAnd(query.getCriteria());
query.setCriteria(null);
List<GroupSymbol> groups = query.getFrom().getGroups();
TreeSet<String> names = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
for (GroupSymbol gs : groups) {
names.add(gs.getName());
}
for (Iterator<Criteria> crits = current.iterator(); crits.hasNext();) {
PlannedResult plannedResult = rmc.findSubquery(crits.next(), context!=null?context.getOptions().isSubqueryUnnestDefault():false);
if (plannedResult.not || plannedResult.query == null || plannedResult.query.getProcessorPlan() != null
|| plannedResult.query.getWith() != null) {
continue;
}
determineCorrelatedReferences(groups, plannedResult);
boolean requiresDistinct = requiresDistinctRows(query);
if (!rmc.planQuery(groups, requiresDistinct, plannedResult)) {
continue;
}
crits.remove();
convertToJoin(plannedResult, names, query, false);
//transform the query into an inner join
}
query.setCriteria(Criteria.combineCriteria(query.getCriteria(), Criteria.combineCriteria(current)));
}
private Query convertToJoin(PlannedResult plannedResult, Set<String> names, Query query, boolean leftOuter) throws QueryResolverException, QueryMetadataException, TeiidComponentException {
GroupSymbol viewName = RulePlaceAccess.recontextSymbol(new GroupSymbol("X"), names); //$NON-NLS-1$
viewName.setName(viewName.getName());
viewName.setDefinition(null);
Query q = createInlineViewQuery(viewName, plannedResult.query, metadata, plannedResult.query.getSelect().getProjectedSymbols());
Iterator<Expression> iter = q.getSelect().getProjectedSymbols().iterator();
HashMap<Expression, Expression> expressionMap = new HashMap<Expression, Expression>();
for (Expression symbol : plannedResult.query.getSelect().getProjectedSymbols()) {
expressionMap.put(SymbolMap.getExpression(symbol), SymbolMap.getExpression(iter.next()));
}
for (int i = 0; i < plannedResult.leftExpressions.size(); i++) {
plannedResult.nonEquiJoinCriteria.add(new CompareCriteria(SymbolMap.getExpression((Expression)plannedResult.leftExpressions.get(i)), CompareCriteria.EQ, (Expression)plannedResult.rightExpressions.get(i)));
}
Criteria mappedCriteria = Criteria.combineCriteria(plannedResult.nonEquiJoinCriteria);
ExpressionMappingVisitor.mapExpressions(mappedCriteria, expressionMap);
FromClause clause = q.getFrom().getClauses().get(0);
if (plannedResult.makeInd) {
clause.setMakeInd(new Option.MakeDep());
}
if (leftOuter) {
FromClause leftClause = query.getFrom().getClauses().get(0);
if (query.getFrom().getClauses().size() > 1) {
leftClause = null;
for (FromClause fc : query.getFrom().getClauses()) {
if (leftClause == null) {
leftClause = fc;
continue;
}
leftClause = new JoinPredicate(leftClause, fc, JoinType.JOIN_CROSS);
}
}
query.getFrom().getClauses().clear();
JoinPredicate jp = new JoinPredicate(leftClause, clause, JoinType.JOIN_LEFT_OUTER);
jp.setJoinCriteria(Criteria.separateCriteriaByAnd(mappedCriteria));
query.getFrom().getClauses().add(jp);
} else {
query.setCriteria(Criteria.combineCriteria(query.getCriteria(), mappedCriteria));
query.getFrom().addClause(clause);
}
query.getTemporaryMetadata().getData().putAll(q.getTemporaryMetadata().getData());
return q;
}
private void determineCorrelatedReferences(List<GroupSymbol> groups,
PlannedResult plannedResult) {
if (plannedResult.query.getCorrelatedReferences() == null) {
//create the correlated refs if they exist
//there is a little bit of a design problem here that null usually means no refs.
ArrayList<Reference> correlatedReferences = new ArrayList<Reference>();
CorrelatedReferenceCollectorVisitor.collectReferences(plannedResult.query, groups, correlatedReferences, metadata);
if (!correlatedReferences.isEmpty()) {
SymbolMap map = new SymbolMap();
for (Reference reference : correlatedReferences) {
map.addMapping(reference.getExpression(), reference.getExpression());
}
plannedResult.query.setCorrelatedReferences(map);
}
}
}
private boolean requiresDistinctRows(Query query) {
Set<AggregateSymbol> aggs = new HashSet<AggregateSymbol>();
aggs.addAll(AggregateSymbolCollectorVisitor.getAggregates(query.getSelect(), false));
aggs.addAll(AggregateSymbolCollectorVisitor.getAggregates(query.getHaving(), false));
if (!aggs.isEmpty() || query.getGroupBy() != null) {
if (!AggregateSymbol.areAggregatesCardinalityDependent(aggs)) {
return false;
}
} else if (query.getSelect().isDistinct()) {
for (Expression projectSymbol : query.getSelect().getProjectedSymbols()) {
Expression ex = SymbolMap.getExpression(projectSymbol);
if (FunctionCollectorVisitor.isNonDeterministic(ex)) {
return true;
}
if (!ValueIteratorProviderCollectorVisitor.getValueIteratorProviders(ex).isEmpty()) {
return true;
}
}
return false;
}
return true;
}
private Query rewriteGroupBy(Query query) throws TeiidComponentException, TeiidProcessingException {
if (query.getGroupBy() == null) {
rewriteAggs = false;
return query;
}
if (isDistinctWithGroupBy(query)) {
query.getSelect().setDistinct(false);
}
rewriteExpressions(query.getGroupBy());
List<Expression> expr = query.getGroupBy().getSymbols();
for (Iterator<Expression> iter = expr.iterator(); iter.hasNext();) {
if (EvaluatableVisitor.willBecomeConstant(iter.next())) {
iter.remove();
}
}
if (expr.isEmpty()) {
query.setGroupBy(null);
}
return query;
}
public static boolean isDistinctWithGroupBy(Query query) {
GroupBy groupBy = query.getGroupBy();
if (groupBy == null) {
return false;
}
HashSet<Expression> selectExpressions = new HashSet<Expression>();
for (Expression selectExpr : query.getSelect().getProjectedSymbols()) {
selectExpressions.add(SymbolMap.getExpression(selectExpr));
}
for (Expression groupByExpr : groupBy.getSymbols()) {
if (!selectExpressions.contains(groupByExpr)) {
return false;
}
}
return true;
}
private void rewriteExpressions(LanguageObject obj) throws TeiidComponentException, TeiidProcessingException{
if (obj == null) {
return;
}
ExpressionMappingVisitor visitor = new ExpressionMappingVisitor(null) {
/**
* @see org.teiid.query.sql.visitor.ExpressionMappingVisitor#replaceExpression(org.teiid.query.sql.symbol.Expression)
*/
@Override
public Expression replaceExpression(Expression element) {
try {
return rewriteExpressionDirect(element);
} catch (TeiidException err) {
throw new TeiidRuntimeException(err);
}
}
};
try {
PostOrderNavigator.doVisit(obj, visitor);
} catch (TeiidRuntimeException err) {
if (err.getCause() instanceof TeiidComponentException) {
throw (TeiidComponentException)err.getCause();
}
if (err.getCause() instanceof TeiidProcessingException) {
throw (TeiidProcessingException)err.getCause();
}
throw err;
}
}
/**
* Rewrite the order by clause.
* Unrelated order by expressions will cause the creation of nested inline views.
*
* @param query
* @throws TeiidComponentException, MetaMatrixProcessingException
* @throws TeiidProcessingException
*/
public QueryCommand rewriteOrderBy(QueryCommand queryCommand) throws TeiidComponentException, TeiidProcessingException {
final OrderBy orderBy = queryCommand.getOrderBy();
if (orderBy == null) {
return queryCommand;
}
Select select = queryCommand.getProjectedQuery().getSelect();
final List<Expression> projectedSymbols = select.getProjectedSymbols();
rewriteOrderBy(queryCommand, orderBy, projectedSymbols, context, metadata);
return queryCommand;
}
public static void rewriteOrderBy(QueryCommand queryCommand,
final OrderBy orderBy, final List projectedSymbols, CommandContext context, QueryMetadataInterface metadata) throws TeiidComponentException, TeiidProcessingException {
HashSet<Expression> previousExpressions = new HashSet<Expression>();
for (int i = 0; i < orderBy.getVariableCount(); i++) {
Expression querySymbol = orderBy.getVariable(i);
int index = orderBy.getExpressionPosition(i);
if (index == -1) {
querySymbol = rewriteExpression(querySymbol, context, metadata);
} else {
querySymbol = (Expression)projectedSymbols.get(index);
}
Expression expr = SymbolMap.getExpression(querySymbol);
if (!previousExpressions.add(expr) || (queryCommand instanceof Query && EvaluatableVisitor.willBecomeConstant(expr))) {
orderBy.removeOrderByItem(i--);
} else {
orderBy.getOrderByItems().get(i).setSymbol((Expression)querySymbol.clone());
}
}
if (orderBy.getVariableCount() == 0) {
queryCommand.setOrderBy(null);
}
}
/**
* This method will alias each of the select into elements to the corresponding column name in the
* target table. This ensures that they will all be uniquely named.
*
* @param query
* @throws QueryValidatorException
*/
private Command rewriteSelectInto(Query query) throws TeiidProcessingException{
Into into = query.getInto();
try {
List<ElementSymbol> allIntoElements = Util.deepClone(ResolverUtil.resolveElementsInGroup(into.getGroup(), metadata), ElementSymbol.class);
Insert insert = new Insert(into.getGroup(), allIntoElements, Collections.emptyList());
insert.setSourceHint(query.getSourceHint());
query.setSourceHint(null);
query.setInto(null);
insert.setQueryExpression(query);
return rewriteInsert(correctDatatypes(insert));
} catch (QueryMetadataException e) {
throw new QueryValidatorException(e);
} catch (TeiidComponentException e) {
throw new QueryValidatorException(e);
}
}
private Insert correctDatatypes(Insert insert) {
boolean needsView = false;
for (int i = 0; !needsView && i < insert.getVariables().size(); i++) {
Expression ses = insert.getVariables().get(i);
if (ses.getType() != insert.getQueryExpression().getProjectedSymbols().get(i).getType()) {
needsView = true;
}
}
if (needsView) {
try {
insert.setQueryExpression(createInlineViewQuery(new GroupSymbol("X"), insert.getQueryExpression(), metadata, insert.getVariables())); //$NON-NLS-1$
} catch (TeiidException err) {
throw new TeiidRuntimeException(QueryPlugin.Event.TEIID30371, err);
}
}
return insert;
}
private void correctProjectedTypes(List actualSymbolTypes, Query query) {
List symbols = query.getSelect().getProjectedSymbols();
List newSymbols = SetQuery.getTypedProjectedSymbols(symbols, actualSymbolTypes, this.metadata);
query.getSelect().setSymbols(newSymbols);
}
private SetQuery rewriteSetQuery(SetQuery setQuery)
throws TeiidComponentException, TeiidProcessingException{
if (setQuery.getProjectedTypes() != null) {
for (QueryCommand command : setQuery.getQueryCommands()) {
if (!(command instanceof Query)) {
continue;
}
correctProjectedTypes(setQuery.getProjectedTypes(), (Query)command);
}
setQuery.setProjectedTypes(null, null);
}
setQuery.setLeftQuery((QueryCommand)rewriteCommand(setQuery.getLeftQuery(), true));
setQuery.setRightQuery((QueryCommand)rewriteCommand(setQuery.getRightQuery(), true));
rewriteOrderBy(setQuery);
if (setQuery.getLimit() != null) {
setQuery.setLimit(rewriteLimitClause(setQuery.getLimit()));
}
return setQuery;
}
private FromClause rewriteFromClause(Query parent, FromClause clause)
throws TeiidComponentException, TeiidProcessingException{
if(clause instanceof JoinPredicate) {
return rewriteJoinPredicate(parent, (JoinPredicate) clause);
} else if (clause instanceof SubqueryFromClause) {
rewriteSubqueryContainer((SubqueryFromClause)clause, true);
} else if (clause instanceof TextTable) {
TextTable tt = (TextTable)clause;
tt.setFile(rewriteExpressionDirect(tt.getFile()));
} else if (clause instanceof XMLTable) {
rewriteExpressions(clause);
} else if (clause instanceof ObjectTable) {
rewriteExpressions(clause);
} else if (clause instanceof ArrayTable) {
ArrayTable at = (ArrayTable)clause;
at.setArrayValue(rewriteExpressionDirect(at.getArrayValue()));
}
return clause;
}
private JoinPredicate rewriteJoinPredicate(Query parent, JoinPredicate predicate)
throws TeiidComponentException, TeiidProcessingException{
List joinCrits = predicate.getJoinCriteria();
if(joinCrits != null && joinCrits.size() > 0) {
//rewrite join crits by rewriting a compound criteria
Criteria criteria = new CompoundCriteria(new ArrayList(joinCrits));
criteria = rewriteCriteria(criteria);
joinCrits.clear();
if (criteria instanceof CompoundCriteria && ((CompoundCriteria)criteria).getOperator() == CompoundCriteria.AND) {
joinCrits.addAll(((CompoundCriteria)criteria).getCriteria());
} else {
joinCrits.add(criteria);
}
predicate.setJoinCriteria(joinCrits);
}
if (predicate.getJoinType() == JoinType.JOIN_UNION) {
predicate.setJoinType(JoinType.JOIN_FULL_OUTER);
predicate.setJoinCriteria(new ArrayList<Criteria>(Arrays.asList(FALSE_CRITERIA)));
} else if (predicate.getJoinType() == JoinType.JOIN_RIGHT_OUTER) {
predicate.setJoinType(JoinType.JOIN_LEFT_OUTER);
FromClause leftClause = predicate.getLeftClause();
predicate.setLeftClause(predicate.getRightClause());
predicate.setRightClause(leftClause);
}
predicate.setLeftClause( rewriteFromClause(parent, predicate.getLeftClause()));
predicate.setRightClause( rewriteFromClause(parent, predicate.getRightClause()));
return predicate;
}
/**
* Rewrite the criteria by evaluating some trivial cases.
* @param criteria The criteria to rewrite
* @param metadata
* @param userCriteria The criteria on user's command, used in rewriting HasCriteria
* in the procedural language.
* @return The re-written criteria
*/
public static Criteria rewriteCriteria(Criteria criteria, CommandContext context, QueryMetadataInterface metadata) throws TeiidComponentException, TeiidProcessingException{
return new QueryRewriter(metadata, context).rewriteCriteria(criteria);
}
/**
* Rewrite the criteria by evaluating some trivial cases.
* @param criteria The criteria to rewrite
* @param userCriteria The criteria on user's command, used in rewriting HasCriteria
* in the procedural language.
* @return The re-written criteria
*/
private Criteria rewriteCriteria(Criteria criteria) throws TeiidComponentException, TeiidProcessingException{
if(criteria instanceof CompoundCriteria) {
return rewriteCriteria((CompoundCriteria)criteria, true);
} else if(criteria instanceof NotCriteria) {
criteria = rewriteCriteria((NotCriteria)criteria);
} else if(criteria instanceof CompareCriteria) {
criteria = rewriteCriteria((CompareCriteria)criteria);
} else if(criteria instanceof SubqueryCompareCriteria) {
criteria = rewriteCriteria((SubqueryCompareCriteria)criteria);
} else if(criteria instanceof MatchCriteria) {
criteria = rewriteCriteria((MatchCriteria)criteria);
} else if(criteria instanceof SetCriteria) {
criteria = rewriteCriteria((SetCriteria)criteria);
} else if(criteria instanceof IsNullCriteria) {
criteria = rewriteCriteria((IsNullCriteria)criteria);
} else if(criteria instanceof BetweenCriteria) {
criteria = rewriteCriteria((BetweenCriteria)criteria);
} else if (criteria instanceof ExistsCriteria) {
ExistsCriteria exists = (ExistsCriteria)criteria;
if (exists.shouldEvaluate() && processing) {
return getCriteria(evaluator.evaluate(exists, null));
}
rewriteSubqueryContainer((SubqueryContainer)criteria, true);
if (!RelationalNodeUtil.shouldExecute(exists.getCommand(), false, true)) {
return exists.isNegated()?TRUE_CRITERIA:FALSE_CRITERIA;
}
if (exists.getCommand().getProcessorPlan() == null) {
if (exists.getCommand() instanceof Query) {
Query query = (Query)exists.getCommand();
if ((query.getLimit() == null || query.getOrderBy() == null) && query.getSelect().getProjectedSymbols().size() > 1) {
query.getSelect().clearSymbols();
query.getSelect().addSymbol(new ExpressionSymbol("x", new Constant(1))); //$NON-NLS-1$
}
}
addImplicitLimit(exists, 1);
}
} else if (criteria instanceof SubquerySetCriteria) {
SubquerySetCriteria sub = (SubquerySetCriteria)criteria;
rewriteWithExplicitArray(sub.getExpression(), sub);
rewriteSubqueryContainer(sub, true);
if (!RelationalNodeUtil.shouldExecute(sub.getCommand(), false, true)) {
return sub.isNegated()?TRUE_CRITERIA:FALSE_CRITERIA;
}
if (rewriteLeftExpression(sub)) {
addImplicitLimit(sub, 1);
}
} else if (criteria instanceof DependentSetCriteria) {
criteria = rewriteDependentSetCriteria((DependentSetCriteria)criteria);
} else if (criteria instanceof ExpressionCriteria) {
return rewriteCriteria(new CompareCriteria(((ExpressionCriteria) criteria).getExpression(), CompareCriteria.EQ, new Constant(Boolean.TRUE)));
}
return evaluateCriteria(criteria);
}
private void rewriteWithExplicitArray(Expression ex, SubqueryContainer<QueryCommand> sub)
throws QueryMetadataException, QueryResolverException,
TeiidComponentException {
if (!(ex instanceof Array) || sub.getCommand() == null || sub.getCommand().getProjectedSymbols().size() == 1) {
return;
}
Query query = QueryRewriter.createInlineViewQuery(new GroupSymbol("x"), sub.getCommand(), metadata, sub.getCommand().getProjectedSymbols()); //$NON-NLS-1$
List<Expression> exprs = new ArrayList<Expression>();
for (Expression expr : query.getSelect().getProjectedSymbols()) {
exprs.add(SymbolMap.getExpression(expr));
}
Array array = new Array(exprs);
query.getSelect().clearSymbols();
query.getSelect().addSymbol(array);
ResolverVisitor.resolveComponentType(array);
sub.setCommand(query);
}
private void addImplicitLimit(SubqueryContainer<QueryCommand> container, int rowLimit) {
if (container.getCommand().getLimit() != null) {
Limit lim = container.getCommand().getLimit();
if (lim.getRowLimit() instanceof Constant) {
Constant c = (Constant)lim.getRowLimit();
if (!c.isMultiValued() && Integer.valueOf(rowLimit).compareTo((Integer) c.getValue()) <= 0) {
lim.setRowLimit(new Constant(rowLimit));
if (lim.getRowLimit() == null) {
lim.setImplicit(true);
container.getCommand().setOrderBy(null);
}
}
}
return;
}
boolean addLimit = true;
if (container.getCommand() instanceof Query) {
Query query = (Query)container.getCommand();
addLimit = !(query.hasAggregates() && query.getGroupBy() == null);
}
if (addLimit) {
Limit lim = new Limit(null, new Constant(rowLimit));
lim.setImplicit(true);
container.getCommand().setLimit(lim);
}
}
private Criteria rewriteDependentSetCriteria(DependentSetCriteria dsc)
throws TeiidComponentException, TeiidProcessingException{
if (!processing) {
if (rewriteLeftExpression(dsc)) {
return UNKNOWN_CRITERIA;
}
}
return dsc;
}
/**
* Performs simple expression flattening
*
* @param criteria
* @return
*/
public static Criteria optimizeCriteria(CompoundCriteria criteria, QueryMetadataInterface metadata) {
try {
return new QueryRewriter(metadata, null).rewriteCriteria(criteria, false);
} catch (TeiidException err) {
//shouldn't happen
return criteria;
}
}
/** May be simplified if this is an AND and a sub criteria is always
* false or if this is an OR and a sub criteria is always true
*/
private Criteria rewriteCriteria(CompoundCriteria criteria, boolean rewrite) throws TeiidComponentException, TeiidProcessingException{
List<Criteria> crits = criteria.getCriteria();
int operator = criteria.getOperator();
// Walk through crits and collect converted ones
LinkedHashSet<Criteria> newCrits = new LinkedHashSet<Criteria>(crits.size());
HashMap<Expression, Criteria> exprMap = new HashMap<Expression, Criteria>();
for (Criteria converted : crits) {
if (rewrite) {
converted = rewriteCriteria(converted);
} else if (converted instanceof CompoundCriteria) {
converted = rewriteCriteria((CompoundCriteria)converted, false);
}
List<Criteria> critList = null;
if (converted instanceof CompoundCriteria) {
CompoundCriteria other = (CompoundCriteria)converted;
if (other.getOperator() == criteria.getOperator()) {
critList = other.getCriteria();
}
}
if (critList == null) {
critList = Arrays.asList(converted);
}
for (Criteria criteria2 : critList) {
converted = criteria2;
//begin boolean optimizations
if(TRUE_CRITERIA.equals(converted)) {
if(operator == CompoundCriteria.OR) {
// this OR must be true as at least one branch is always true
return converted;
}
} else if(FALSE_CRITERIA.equals(converted)) {
if(operator == CompoundCriteria.AND) {
// this AND must be false as at least one branch is always false
return converted;
}
} else if (UNKNOWN_CRITERIA.equals(converted)) {
if (preserveUnknown) {
newCrits.add(converted);
} else {
if(operator == CompoundCriteria.AND) {
return FALSE_CRITERIA;
}
}
} else {
if (operator == CompoundCriteria.AND) {
converted = rewriteAndConjunct(converted, exprMap, newCrits);
if (converted != null) {
return converted;
}
} else {
//or
if (converted instanceof SetCriteria) {
SetCriteria sc = (SetCriteria)converted;
if (!sc.isNegated() && sc.isAllConstants()) {
Criteria crit = exprMap.get(sc.getExpression());
if (crit == null) {
exprMap.put(sc.getExpression(), sc);
} else if (crit instanceof SetCriteria) {
SetCriteria other = (SetCriteria)crit;
other.getValues().addAll(sc.getValues());
continue;
} else {
newCrits.remove(crit);
CompareCriteria cc = (CompareCriteria)crit;
sc.getValues().add(cc.getRightExpression());
}
}
} else if (converted instanceof CompareCriteria) {
CompareCriteria cc = (CompareCriteria)converted;
if (cc.getOperator() == CompareCriteria.EQ && cc.getRightExpression() instanceof Constant) {
Criteria crit = exprMap.get(cc.getLeftExpression());
if (crit == null) {
exprMap.put(cc.getLeftExpression(), cc);
} else if (crit instanceof SetCriteria) {
SetCriteria other = (SetCriteria)crit;
other.getValues().add(cc.getRightExpression());
continue;
} else {
newCrits.remove(crit);
CompareCriteria other = (CompareCriteria)crit;
SetCriteria sc = new SetCriteria(cc.getLeftExpression(), DataTypeManager.isHashable(other.getRightExpression().getType())?new LinkedHashSet<Constant>():new TreeSet<Constant>());
sc.setAllConstants(true);
sc.getValues().add(cc.getRightExpression());
sc.getValues().add(other.getRightExpression());
exprMap.put(sc.getExpression(), sc);
converted = sc;
}
}
}
newCrits.add(converted);
}
}
}
}
if(newCrits.size() == 0) {
if(operator == CompoundCriteria.AND) {
return TRUE_CRITERIA;
}
return FALSE_CRITERIA;
} else if(newCrits.size() == 1) {
// Only one sub crit now, so just return it
return newCrits.iterator().next();
} else {
criteria.getCriteria().clear();
criteria.getCriteria().addAll(newCrits);
return criteria;
}
}
/**
* Rewrite the given conjunct
* @return null if the conjunct was internally handled
*/
private Criteria rewriteAndConjunct(Criteria converted, Map<Expression, Criteria> exprMap, LinkedHashSet<Criteria> newCrits) {
if (converted instanceof IsNullCriteria) {
IsNullCriteria inc = (IsNullCriteria)converted;
if (!inc.isNegated()) {
Criteria crit = exprMap.get(inc.getExpression());
if (crit == null) {
exprMap.put(inc.getExpression(), converted);
} else if (!(crit instanceof IsNullCriteria)) {
return FALSE_CRITERIA;
}
}
} else if (converted instanceof SetCriteria) {
SetCriteria sc = (SetCriteria)converted;
Criteria crit = exprMap.get(sc.getExpression());
if (crit instanceof IsNullCriteria) {
return FALSE_CRITERIA;
}
if (!sc.isNegated() && sc.isAllConstants()) {
if (crit == null) {
exprMap.put(sc.getExpression(), converted);
} else if (crit instanceof SetCriteria) {
SetCriteria sc1 = (SetCriteria)crit;
newCrits.remove(sc1);
sc1.getValues().retainAll(sc.getValues());
if (sc1.getValues().isEmpty()) {
return FALSE_CRITERIA;
}
//TODO: single value as compare criteria
newCrits.add(sc1);
exprMap.put(sc1.getExpression(), sc1);
return null;
} else {
CompareCriteria cc = (CompareCriteria)crit;
for (Iterator<Constant> exprIter = sc.getValues().iterator(); exprIter.hasNext();) {
if (!Evaluator.compare(cc.getOperator(), exprIter.next().getValue(), ((Constant)cc.getRightExpression()).getValue())) {
exprIter.remove();
}
}
if (sc.getValues().isEmpty()) {
return FALSE_CRITERIA;
}
if (cc.getOperator() != CompareCriteria.EQ) {
newCrits.remove(cc);
//TODO: single value as compare criteria
exprMap.put(sc.getExpression(), sc);
} else {
return null;
}
}
}
} else if (converted instanceof CompareCriteria) {
CompareCriteria cc = (CompareCriteria)converted;
Criteria crit = exprMap.get(cc.getLeftExpression());
if (crit instanceof IsNullCriteria) {
return FALSE_CRITERIA;
}
if (cc.getRightExpression() instanceof Constant) {
if (crit == null) {
exprMap.put(cc.getLeftExpression(), cc);
} else if (crit instanceof SetCriteria) {
SetCriteria sc = (SetCriteria)crit;
boolean modified = false;
for (Iterator<Constant> exprIter = sc.getValues().iterator(); exprIter.hasNext();) {
if (!Evaluator.compare(cc.getOperator(), exprIter.next().getValue(), ((Constant)cc.getRightExpression()).getValue())) {
if (!modified) {
modified = true;
newCrits.remove(sc);
}
exprIter.remove();
}
}
//TODO: single value as compare criteria
if (sc.getValues().isEmpty()) {
return FALSE_CRITERIA;
}
if (cc.getOperator() == CompareCriteria.EQ) {
exprMap.put(cc.getLeftExpression(), cc);
} else if (modified) {
if (sc.getNumberOfValues() == 1) {
CompareCriteria comp = new CompareCriteria(sc.getExpression(), CompareCriteria.EQ, (Expression)sc.getValues().iterator().next());
newCrits.add(comp);
exprMap.put(sc.getExpression(), comp);
} else {
newCrits.add(sc);
exprMap.put(sc.getExpression(), sc);
}
return null;
}
} else {
CompareCriteria cc1 = (CompareCriteria)crit;
if (cc1.getOperator() == CompareCriteria.NE) {
exprMap.put(cc.getLeftExpression(), cc);
} else if (cc1.getOperator() == CompareCriteria.EQ) {
if (!Evaluator.compare(cc.getOperator(), ((Constant)cc1.getRightExpression()).getValue(), ((Constant)cc.getRightExpression()).getValue())) {
return FALSE_CRITERIA;
}
return null;
}
if (cc.getOperator() == CompareCriteria.EQ) {
if (!Evaluator.compare(cc1.getOperator(), ((Constant)cc.getRightExpression()).getValue(), ((Constant)cc1.getRightExpression()).getValue())) {
return FALSE_CRITERIA;
}
exprMap.put(cc.getLeftExpression(), cc);
newCrits.remove(cc1);
}
}
}
}
newCrits.add(converted);
return null;
}
private Criteria evaluateCriteria(Criteria crit) throws TeiidComponentException, TeiidProcessingException{
if(EvaluatableVisitor.isFullyEvaluatable(crit, !processing)) {
try {
Boolean eval = evaluator.evaluateTVL(crit, Collections.emptyList());
return getCriteria(eval);
} catch(ExpressionEvaluationException e) {
throw new QueryValidatorException(QueryPlugin.Event.TEIID30372, e, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30372, crit));
}
}
return crit;
}
private Criteria getCriteria(Boolean eval) {
if (eval == null) {
return UNKNOWN_CRITERIA;
}
if(Boolean.TRUE.equals(eval)) {
return TRUE_CRITERIA;
}
return FALSE_CRITERIA;
}
private Criteria rewriteCriteria(NotCriteria criteria) throws TeiidComponentException, TeiidProcessingException{
Criteria innerCrit = criteria.getCriteria();
if (innerCrit instanceof CompoundCriteria) {
//reduce to only negation of predicates, so that the null/unknown handling criteria is applied appropriately
return rewriteCriteria(Criteria.applyDemorgan(innerCrit));
}
if (innerCrit instanceof Negatable) {
((Negatable) innerCrit).negate();
return rewriteCriteria(innerCrit);
}
if (innerCrit instanceof NotCriteria) {
return rewriteCriteria(((NotCriteria)innerCrit).getCriteria());
}
innerCrit = rewriteCriteria(innerCrit);
if (innerCrit instanceof Negatable) {
((Negatable) innerCrit).negate();
return rewriteCriteria(innerCrit);
}
if (innerCrit instanceof NotCriteria) {
return rewriteCriteria(((NotCriteria)innerCrit).getCriteria());
}
if(innerCrit == TRUE_CRITERIA) {
return FALSE_CRITERIA;
} else if(innerCrit == FALSE_CRITERIA) {
return TRUE_CRITERIA;
} else if (innerCrit == UNKNOWN_CRITERIA) {
return UNKNOWN_CRITERIA;
}
criteria.setCriteria(innerCrit);
return criteria;
}
/**
* Rewrites "a [NOT] BETWEEN b AND c" as "a >= b AND a <= c", or as "a <= b OR a>= c"
* @param criteria
* @return
* @throws QueryValidatorException
*/
private Criteria rewriteCriteria(BetweenCriteria criteria) throws TeiidComponentException, TeiidProcessingException{
CompareCriteria lowerCriteria = new CompareCriteria(criteria.getExpression(),
criteria.isNegated() ? CompareCriteria.LT: CompareCriteria.GE,
criteria.getLowerExpression());
CompareCriteria upperCriteria = new CompareCriteria(criteria.getExpression(),
criteria.isNegated() ? CompareCriteria.GT: CompareCriteria.LE,
criteria.getUpperExpression());
CompoundCriteria newCriteria = new CompoundCriteria(criteria.isNegated() ? CompoundCriteria.OR : CompoundCriteria.AND,
lowerCriteria,
upperCriteria);
return rewriteCriteria(newCriteria);
}
private Criteria rewriteCriteria(CompareCriteria criteria) throws TeiidComponentException, TeiidProcessingException{
Expression leftExpr = rewriteExpressionDirect(criteria.getLeftExpression());
Expression rightExpr = rewriteExpressionDirect(criteria.getRightExpression());
criteria.setLeftExpression(leftExpr);
criteria.setRightExpression(rightExpr);
if (isNull(leftExpr) || isNull(rightExpr)) {
return UNKNOWN_CRITERIA;
}
if (leftExpr.equals(rightExpr)) {
switch(criteria.getOperator()) {
case CompareCriteria.LE:
case CompareCriteria.GE:
case CompareCriteria.EQ:
if (leftExpr instanceof Constant) {
return TRUE_CRITERIA;
}
return getSimpliedCriteria(criteria, criteria.getLeftExpression(), true, true);
default:
if (leftExpr instanceof Constant) {
return FALSE_CRITERIA;
}
return getSimpliedCriteria(criteria, criteria.getLeftExpression(), false, true);
}
}
boolean rightConstant = false;
if(EvaluatableVisitor.willBecomeConstant(rightExpr)) {
rightConstant = true;
} else if (EvaluatableVisitor.willBecomeConstant(leftExpr)) {
// Swap in this particular case for connectors
criteria.setLeftExpression(rightExpr);
criteria.setRightExpression(leftExpr);
// Check for < or > operator as we have to switch it
switch(criteria.getOperator()) {
case CompareCriteria.LT: criteria.setOperator(CompareCriteria.GT); break;
case CompareCriteria.LE: criteria.setOperator(CompareCriteria.GE); break;
case CompareCriteria.GT: criteria.setOperator(CompareCriteria.LT); break;
case CompareCriteria.GE: criteria.setOperator(CompareCriteria.LE); break;
}
rightConstant = true;
}
Function f = null;
while (rightConstant && f != criteria.getLeftExpression() && criteria.getLeftExpression() instanceof Function) {
f = (Function)criteria.getLeftExpression();
Criteria result = simplifyWithInverse(criteria);
if (!(result instanceof CompareCriteria)) {
return result;
}
criteria = (CompareCriteria)result;
}
Criteria modCriteria = simplifyTimestampMerge(criteria);
if(modCriteria instanceof CompareCriteria) {
modCriteria = simplifyTimestampMerge2((CompareCriteria)modCriteria);
}
return modCriteria;
}
public static boolean isNull(Expression expr) {
return expr instanceof Constant && ((Constant)expr).isNull();
}
/*
* The thing of primary importance here is that the use of the 'ANY' predicate
* quantifier is replaced with the canonical and equivalent 'SOME'
*/
private Criteria rewriteCriteria(SubqueryCompareCriteria criteria) throws TeiidComponentException, TeiidProcessingException{
rewriteWithExplicitArray(criteria.getArrayExpression(), criteria);
if (criteria.getCommand() != null && criteria.getCommand().getProcessorPlan() == null) {
if ((criteria.getOperator() == CompareCriteria.EQ && criteria.getPredicateQuantifier() != SubqueryCompareCriteria.ALL)
|| (criteria.getOperator() == CompareCriteria.NE && criteria.getPredicateQuantifier() == SubqueryCompareCriteria.ALL)) {
SubquerySetCriteria result = new SubquerySetCriteria(criteria.getLeftExpression(), criteria.getCommand());
result.setNegated(criteria.getOperator() == CompareCriteria.NE);
return rewriteCriteria(result);
}
if (criteria.getPredicateQuantifier() != SubqueryCompareCriteria.ALL && criteria.getOperator() != CompareCriteria.EQ && criteria.getOperator() != CompareCriteria.NE) {
CompareCriteria cc = new CompareCriteria();
cc.setLeftExpression(criteria.getLeftExpression());
boolean useView = true;
if (criteria.getCommand() instanceof Query) {
Query query = (Query)criteria.getCommand();
if (!query.hasAggregates() && query.getCriteria() != null && query.getOrderBy() == null) {
final boolean[] hasWindowFunctions = new boolean[1];
PreOrPostOrderNavigator.doVisit(query.getSelect(), new LanguageVisitor() {
public void visit(WindowFunction windowFunction) {
hasWindowFunctions[0] = true;
};
}, PreOrPostOrderNavigator.PRE_ORDER);
useView = hasWindowFunctions[0];
}
}
AggregateSymbol.Type type = Type.MAX;
if (criteria.getOperator() == CompareCriteria.GT || criteria.getOperator() == CompareCriteria.GE) {
type = Type.MIN;
}
if (useView) {
Query q = createInlineViewQuery(new GroupSymbol("X"), criteria.getCommand(), metadata, criteria.getCommand().getProjectedSymbols()); //$NON-NLS-1$
Expression ses = q.getProjectedSymbols().get(0);
Expression expr = SymbolMap.getExpression(ses);
q.getSelect().clearSymbols();
q.getSelect().addSymbol(new AggregateSymbol(type.name(), false, expr));
ScalarSubquery ss = new ScalarSubquery(q);
ss.setSubqueryHint(criteria.getSubqueryHint());
cc.setRightExpression(ss);
cc.setOperator(criteria.getOperator());
return rewriteCriteria(cc);
}
Select select = ((Query)criteria.getCommand()).getSelect();
Expression ex = select.getProjectedSymbols().get(0);
ex = SymbolMap.getExpression(ex);
select.setSymbols(Arrays.asList(new AggregateSymbol(type.name(), false, ex)));
select.setDistinct(false);
}
}
Expression leftExpr = rewriteExpressionDirect(criteria.getLeftExpression());
if (isNull(leftExpr) && criteria.getCommand() != null) {
addImplicitLimit(criteria, 1);
}
criteria.setLeftExpression(leftExpr);
if (criteria.getPredicateQuantifier() == SubqueryCompareCriteria.ANY){
criteria.setPredicateQuantifier(SubqueryCompareCriteria.SOME);
}
rewriteSubqueryContainer(criteria, true);
if (criteria.getCommand() != null && !RelationalNodeUtil.shouldExecute(criteria.getCommand(), false, true)) {
//TODO: this is not interpreted the same way in all databases
//for example H2 treat both cases as false - however the spec and all major vendors support the following:
if (criteria.getPredicateQuantifier()==SubqueryCompareCriteria.SOME) {
return FALSE_CRITERIA;
}
return TRUE_CRITERIA;
}
return criteria;
}
private Criteria simplifyWithInverse(CompareCriteria criteria) throws TeiidProcessingException{
Expression leftExpr = criteria.getLeftExpression();
Function leftFunction = (Function) leftExpr;
if(isSimpleMathematicalFunction(leftFunction)) {
return simplifyMathematicalCriteria(criteria);
}
if (FunctionLibrary.isConvert(leftFunction)) {
return simplifyConvertFunction(criteria);
}
return simplifyParseFormatFunction(criteria);
}
private boolean isSimpleMathematicalFunction(Function function) {
String funcName = function.getName();
if(funcName.equals("+") || funcName.equals("-") || funcName.equals("*") || funcName.equals("/")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
Expression[] args = function.getArgs();
if(args[0] instanceof Constant || args[1] instanceof Constant) {
return true;
}
}
// fall through - not simple mathematical
return false;
}
/**
* @param criteria
* @return CompareCriteria
*/
private CompareCriteria simplifyMathematicalCriteria(CompareCriteria criteria)
throws TeiidProcessingException{
Expression leftExpr = criteria.getLeftExpression();
Expression rightExpr = criteria.getRightExpression();
// Identify all the pieces of this criteria
Function function = (Function) leftExpr;
String funcName = function.getName();
Expression[] args = function.getArgs();
Constant const1 = null;
Expression expr = null;
if(args[1] instanceof Constant) {
const1 = (Constant) args[1];
expr = args[0];
} else {
if(funcName.equals("+") || funcName.equals("*")) { //$NON-NLS-1$ //$NON-NLS-2$
const1 = (Constant) args[0];
expr = args[1];
} else {
// If we have "5 - x = 10" or "5 / x = 10", abort!
return criteria;
}
}
int operator = criteria.getOperator();
// Determine opposite function
String oppFunc = null;
switch(funcName.charAt(0)) {
case '+': oppFunc = "-"; break; //$NON-NLS-1$
case '-': oppFunc = "+"; break; //$NON-NLS-1$
case '*': oppFunc = "/"; break; //$NON-NLS-1$
case '/': oppFunc = "*"; break; //$NON-NLS-1$
}
// Create a function of the two constants and evaluate it
Expression combinedConst = null;
FunctionLibrary funcLib = this.metadata.getFunctionLibrary();
FunctionDescriptor descriptor = funcLib.findFunction(oppFunc, new Class[] { rightExpr.getType(), const1.getType() });
if (descriptor == null){
//See defect 9380 - this can be caused by const2 being a null Constant, for example (? + 1) < null
return criteria;
}
if (rightExpr instanceof Constant) {
Constant const2 = (Constant)rightExpr;
try {
Object result = descriptor.invokeFunction(new Object[] { const2.getValue(), const1.getValue() }, null, this.context );
combinedConst = new Constant(result, descriptor.getReturnType());
} catch(FunctionExecutionException e) {
throw new QueryValidatorException(QueryPlugin.Event.TEIID30373, e, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30373, e.getMessage()));
} catch (BlockedException e) {
throw new QueryValidatorException(QueryPlugin.Event.TEIID30373, e, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30373, e.getMessage()));
}
} else {
Function conversion = new Function(descriptor.getName(), new Expression[] { rightExpr, const1 });
conversion.setType(leftExpr.getType());
conversion.setFunctionDescriptor(descriptor);
combinedConst = conversion;
}
// Flip operator if necessary
if(! (operator == CompareCriteria.EQ || operator == CompareCriteria.NE) &&
(oppFunc.equals("*") || oppFunc.equals("/")) ) { //$NON-NLS-1$ //$NON-NLS-2$
Object value = const1.getValue();
if(value != null) {
Class type = const1.getType();
Comparable comparisonObject = null;
if(type.equals(DataTypeManager.DefaultDataClasses.INTEGER)) {
comparisonObject = INTEGER_ZERO;
} else if(type.equals(DataTypeManager.DefaultDataClasses.DOUBLE)) {
comparisonObject = DOUBLE_ZERO;
} else if(type.equals(DataTypeManager.DefaultDataClasses.FLOAT)) {
comparisonObject = FLOAT_ZERO;
} else if(type.equals(DataTypeManager.DefaultDataClasses.LONG)) {
comparisonObject = LONG_ZERO;
} else if(type.equals(DataTypeManager.DefaultDataClasses.BIG_INTEGER)) {
comparisonObject = BIG_INTEGER_ZERO;
} else if(type.equals(DataTypeManager.DefaultDataClasses.BIG_DECIMAL)) {
comparisonObject = BIG_DECIMAL_ZERO;
} else if(type.equals(DataTypeManager.DefaultDataClasses.SHORT)) {
comparisonObject = SHORT_ZERO;
} else if(type.equals(DataTypeManager.DefaultDataClasses.BYTE)) {
comparisonObject = BYTE_ZERO;
} else {
// Unknown type
return criteria;
}
// If value is less than comparison object (which is zero),
// then need to switch operator.
if(comparisonObject.compareTo(value) > 0) {
switch(operator) {
case CompareCriteria.LE: operator = CompareCriteria.GE; break;
case CompareCriteria.LT: operator = CompareCriteria.GT; break;
case CompareCriteria.GE: operator = CompareCriteria.LE; break;
case CompareCriteria.GT: operator = CompareCriteria.LT; break;
}
}
}
}
criteria.setLeftExpression(expr);
criteria.setRightExpression(combinedConst);
criteria.setOperator(operator);
// Return new simplified criteria
return criteria;
}
/**
* This method attempts to rewrite compare criteria of the form
*
* <code>convert(typedColumn, string) = '5'</code>
*
* into
*
* <code>typedColumn = convert('5', typeOfColumn)</code>
* where 'typeOfColumn' is the type of 'typedColumn'
*
* if, for example, the type of the column is integer, than the above
* can be pre-evaluated to
*
* <code>typedColumn = 5 </code>
*
* Right expression has already been checked to be a Constant, left expression to be
* a function. Function is known to be "convert" or "cast".
*
* @param crit CompareCriteria
* @return same Criteria instance (possibly optimized)
* @throws QueryValidatorException
* @since 4.2
*/
private Criteria simplifyConvertFunction(CompareCriteria crit) {
Function leftFunction = (Function) crit.getLeftExpression();
Expression leftExpr = leftFunction.getArgs()[0];
if(!(crit.getRightExpression() instanceof Constant)
//TODO: this can be relaxed for order preserving operations
|| !(crit.getOperator() == CompareCriteria.EQ || crit.getOperator() == CompareCriteria.NE)) {
return crit;
}
Constant rightConstant = (Constant) crit.getRightExpression();
String leftExprTypeName = DataTypeManager.getDataTypeName(leftExpr.getType());
Constant result = ResolverUtil.convertConstant(DataTypeManager.getDataTypeName(rightConstant.getType()), leftExprTypeName, rightConstant);
if (result == null) {
return getSimpliedCriteria(crit, leftExpr, crit.getOperator() != CompareCriteria.EQ, true);
}
Constant other = ResolverUtil.convertConstant(leftExprTypeName, DataTypeManager.getDataTypeName(rightConstant.getType()), result);
if (other == null || rightConstant.compareTo(other) != 0) {
return getSimpliedCriteria(crit, leftExpr, crit.getOperator() != CompareCriteria.EQ, true);
}
if (!DataTypeManager.isImplicitConversion(leftExprTypeName, DataTypeManager.getDataTypeName(rightConstant.getType()))) {
return crit;
}
crit.setRightExpression(result);
crit.setLeftExpression(leftExpr);
return crit;
}
/**
* This method attempts to rewrite set criteria of the form
*
* <code>convert(typedColumn, string) in ('5', '6')</code>
*
* into
*
* <code>typedColumn in (convert('5', typeOfColumn), convert('6', typeOfColumn)) </code>
* where 'typeOfColumn' is the type of 'typedColumn'
*
* if, for example, the type of the column is integer, than the above
* can be pre-evaluated to
*
* <code>typedColumn in (5,6) </code>
*
* Right expression has already been checked to be a Constant, left expression to be
* a function. Function is known to be "convert" or "cast". The scope of this change
* will be limited to the case where the left expression is attempting to convert to
* 'string'.
*
* @param crit CompareCriteria
* @return same Criteria instance (possibly optimized)
* @throws QueryValidatorException
* @since 4.2
*/
private Criteria simplifyConvertFunction(SetCriteria crit) throws TeiidComponentException, TeiidProcessingException{
Function leftFunction = (Function) crit.getExpression();
Expression leftExpr = leftFunction.getArgs()[0];
String leftExprTypeName = DataTypeManager.getDataTypeName(leftExpr.getType());
Iterator i = crit.getValues().iterator();
Collection newValues = new ArrayList(crit.getNumberOfValues());
boolean convertedAll = true;
boolean removedSome = false;
while (i.hasNext()) {
Object next = i.next();
if (!(next instanceof Constant)) {
convertedAll = false;
continue;
}
Constant rightConstant = (Constant) next;
Constant result = ResolverUtil.convertConstant(DataTypeManager.getDataTypeName(rightConstant.getType()), leftExprTypeName, rightConstant);
if (result != null) {
Constant other = ResolverUtil.convertConstant(leftExprTypeName, DataTypeManager.getDataTypeName(rightConstant.getType()), result);
if (other == null || ((Comparable)rightConstant.getValue()).compareTo(other.getValue()) != 0) {
result = null;
}
}
if (result == null) {
removedSome = true;
i.remove();
} else if (DataTypeManager.isImplicitConversion(leftExprTypeName, DataTypeManager.getDataTypeName(rightConstant.getType()))) {
newValues.add(result);
} else {
convertedAll = false;
}
}
if (!convertedAll) {
if (!removedSome) {
return crit; //just return as is
}
} else {
crit.setExpression(leftExpr);
crit.setValues(newValues);
}
return rewriteCriteria(crit);
}
private Criteria simplifyParseFormatFunction(CompareCriteria crit) {
//TODO: this can be relaxed for order preserving operations
if(!(crit.getOperator() == CompareCriteria.EQ || crit.getOperator() == CompareCriteria.NE)) {
return crit;
}
boolean isFormat = false;
Function leftFunction = (Function) crit.getLeftExpression();
String funcName = leftFunction.getName();
String inverseFunction = null;
if(StringUtil.startsWithIgnoreCase(funcName, "parse")) { //$NON-NLS-1$
String type = funcName.substring(5);
if (!PARSE_FORMAT_TYPES.contains(type)) {
return crit;
}
inverseFunction = "format" + type; //$NON-NLS-1$
} else if(StringUtil.startsWithIgnoreCase(funcName, "format")) { //$NON-NLS-1$
String type = funcName.substring(6);
if (!PARSE_FORMAT_TYPES.contains(type)) {
return crit;
}
inverseFunction = "parse" + type; //$NON-NLS-1$
isFormat = true;
} else {
return crit;
}
Expression rightExpr = crit.getRightExpression();
if (!(rightExpr instanceof Constant)) {
return crit;
}
Expression leftExpr = leftFunction.getArgs()[0];
Expression formatExpr = leftFunction.getArgs()[1];
if(!(formatExpr instanceof Constant)) {
return crit;
}
String format = (String)((Constant)formatExpr).getValue();
FunctionLibrary funcLib = this.metadata.getFunctionLibrary();
FunctionDescriptor descriptor = funcLib.findFunction(inverseFunction, new Class[] { rightExpr.getType(), formatExpr.getType() });
if(descriptor == null){
return crit;
}
Object value = ((Constant)rightExpr).getValue();
try {
Object result = descriptor.invokeFunction(new Object[] {context, ((Constant)rightExpr).getValue(), format}, null, this.context );
result = leftFunction.getFunctionDescriptor().invokeFunction(new Object[] {context, result, format }, null, this.context );
if (Constant.COMPARATOR.compare(value, result) != 0) {
return getSimpliedCriteria(crit, leftExpr, crit.getOperator() != CompareCriteria.EQ, true);
}
} catch(FunctionExecutionException e) {
//Not all numeric formats are invertable, so just return the criteria as it may still be valid
return crit;
} catch (BlockedException e) {
return crit;
}
//parseFunctions are all potentially narrowing
if (!isFormat) {
return crit;
}
//TODO: if format is not lossy, then invert the function
return crit;
}
/**
* This method applies a similar simplification as the previous method for Case 1829. This is conceptually
* the same thing but done using the timestampCreate system function.
*
* TIMESTAMPCREATE(rpcolli_physical.RPCOLLI.Table_B.date_field, rpcolli_physical.RPCOLLI.Table_B.time_field)
* = {ts'1969-09-20 18:30:45.0'}
*
* -------------
*
* rpcolli_physical.RPCOLLI.Table_B.date_field = {d'1969-09-20'}
* AND
* rpcolli_physical.RPCOLLI.Table_B.time_field = {t'18:30:45'}
*
*
* @param criteria Compare criteria
* @return Simplified criteria, if possible
*/
private Criteria simplifyTimestampMerge2(CompareCriteria criteria) {
if(criteria.getOperator() != CompareCriteria.EQ) {
return criteria;
}
Expression leftExpr = criteria.getLeftExpression();
Expression rightExpr = criteria.getRightExpression();
// Allow for concat and string literal to be on either side
Function tsCreateFunction = null;
Constant timestampConstant = null;
if(leftExpr instanceof Function && rightExpr instanceof Constant) {
tsCreateFunction = (Function) leftExpr;
timestampConstant = (Constant) rightExpr;
} else {
return criteria;
}
// Verify data type of constant and that constant has a value
if(! timestampConstant.getType().equals(DataTypeManager.DefaultDataClasses.TIMESTAMP)) {
return criteria;
}
// Verify function is timestampCreate function
if(! (tsCreateFunction.getName().equalsIgnoreCase("timestampCreate"))) { //$NON-NLS-1$
return criteria;
}
// Get timestamp literal and break into pieces
Timestamp ts = (Timestamp) timestampConstant.getValue();
String tsStr = ts.toString();
Date date = Date.valueOf(tsStr.substring(0, 10));
Time time = Time.valueOf(tsStr.substring(11, 19));
// Get timestampCreate args
Expression[] args = tsCreateFunction.getArgs();
// Rebuild the function
CompareCriteria dateCrit = new CompareCriteria(args[0], CompareCriteria.EQ, new Constant(date, DataTypeManager.DefaultDataClasses.DATE));
CompareCriteria timeCrit = new CompareCriteria(args[1], CompareCriteria.EQ, new Constant(time, DataTypeManager.DefaultDataClasses.TIME));
CompoundCriteria compCrit = new CompoundCriteria(CompoundCriteria.AND, dateCrit, timeCrit);
return compCrit;
}
/**
* This method also applies the same simplification for Case 1829. This is conceptually
* the same thing but done using the timestampCreate system function.
*
* formatDate(rpcolli_physical.RPCOLLI.Table_B.date_field, 'yyyy-MM-dd')
* || formatTime(rpcolli_physical.RPCOLLI.Table_B.time_field, ' HH:mm:ss') = '1969-09-20 18:30:45'
*
* -------------
*
* rpcolli_physical.RPCOLLI.Table_B.date_field = {d'1969-09-20'}
* AND
* rpcolli_physical.RPCOLLI.Table_B.time_field = {t'18:30:45'}
*
*
* @param criteria Compare criteria
* @return Simplified criteria, if possible
*/
private Criteria simplifyTimestampMerge(CompareCriteria criteria) {
if(criteria.getOperator() != CompareCriteria.EQ) {
return criteria;
}
Expression leftExpr = criteria.getLeftExpression();
Expression rightExpr = criteria.getRightExpression();
// Allow for concat and string literal to be on either side
Function concatFunction = null;
Constant timestampConstant = null;
if(leftExpr instanceof Function && rightExpr instanceof Constant) {
concatFunction = (Function) leftExpr;
timestampConstant = (Constant) rightExpr;
} else {
return criteria;
}
// Verify data type of string constant and that constant has a value
if(! timestampConstant.getType().equals(DataTypeManager.DefaultDataClasses.STRING)) {
return criteria;
}
// Verify function is concat function
if(! (concatFunction.getName().equalsIgnoreCase("concat") || concatFunction.getName().equals("||"))) { //$NON-NLS-1$ //$NON-NLS-2$
return criteria;
}
// Verify concat has formatdate and formattime functions
Expression[] args = concatFunction.getArgs();
if(! (args[0] instanceof Function && args[1] instanceof Function)) {
return criteria;
}
Function formatDateFunction = (Function) args[0];
Function formatTimeFunction = (Function) args[1];
if(! (formatDateFunction.getName().equalsIgnoreCase("formatdate") && formatTimeFunction.getName().equalsIgnoreCase("formattime"))) { //$NON-NLS-1$ //$NON-NLS-2$
return criteria;
}
// Verify format functions have constants
if(! (formatDateFunction.getArgs()[1] instanceof Constant && formatTimeFunction.getArgs()[1] instanceof Constant)) {
return criteria;
}
// Verify length of combined date/time constants == timestamp constant
String dateFormat = (String) ((Constant)formatDateFunction.getArgs()[1]).getValue();
String timeFormat = (String) ((Constant)formatTimeFunction.getArgs()[1]).getValue();
String timestampValue = (String) timestampConstant.getValue();
// Passed all the checks, so build the optimized version
try {
Timestamp ts = FunctionMethods.parseTimestamp(this.context, timestampValue, dateFormat + timeFormat);
Constant dateConstant = new Constant(TimestampWithTimezone.createDate(ts));
CompareCriteria dateCompare = new CompareCriteria(formatDateFunction.getArgs()[0], CompareCriteria.EQ, dateConstant);
Constant timeConstant = new Constant(TimestampWithTimezone.createTime(ts));
CompareCriteria timeCompare = new CompareCriteria(formatTimeFunction.getArgs()[0], CompareCriteria.EQ, timeConstant);
CompoundCriteria compCrit = new CompoundCriteria(CompoundCriteria.AND, dateCompare, timeCompare);
return compCrit;
} catch(FunctionExecutionException e) {
return criteria;
}
}
private Criteria rewriteCriteria(MatchCriteria criteria) throws TeiidComponentException, TeiidProcessingException{
criteria.setLeftExpression( rewriteExpressionDirect(criteria.getLeftExpression()));
criteria.setRightExpression( rewriteExpressionDirect(criteria.getRightExpression()));
if (isNull(criteria.getLeftExpression()) || isNull(criteria.getRightExpression())) {
return UNKNOWN_CRITERIA;
}
Expression rightExpr = criteria.getRightExpression();
if(rightExpr instanceof Constant && rightExpr.getType().equals(DataTypeManager.DefaultDataClasses.STRING)) {
Constant constant = (Constant) rightExpr;
String value = (String) constant.getValue();
if (criteria.getMode() != MatchMode.REGEX) {
char escape = criteria.getEscapeChar();
// Check whether escape char is unnecessary and remove it
if(escape != MatchCriteria.NULL_ESCAPE_CHAR && value.indexOf(escape) < 0) {
criteria.setEscapeChar(MatchCriteria.NULL_ESCAPE_CHAR);
}
// if the value of this string constant is '%', then we know the crit will
// always be true
if ( value.equals( String.valueOf(MatchCriteria.WILDCARD_CHAR)) ) {
return getSimpliedCriteria(criteria, criteria.getLeftExpression(), !criteria.isNegated(), true);
}
if (criteria.getMode() == MatchMode.SIMILAR) {
//regex is more widely supported
criteria.setMode(MatchMode.REGEX);
criteria.setRightExpression(new Constant(Evaluator.SIMILAR_TO_REGEX.getPatternString(value, escape)));
criteria.setEscapeChar(MatchCriteria.NULL_ESCAPE_CHAR);
} else if(DataTypeManager.DefaultDataClasses.STRING.equals(criteria.getLeftExpression().getType())
&& value.indexOf(escape) < 0
&& value.indexOf(MatchCriteria.MATCH_CHAR) < 0
&& value.indexOf(MatchCriteria.WILDCARD_CHAR) < 0) {
// if both left and right expressions are strings, and the LIKE match characters ('*', '_') are not present
// in the right expression, rewrite the criteria as EQUALs rather than LIKE
return rewriteCriteria(new CompareCriteria(criteria.getLeftExpression(), criteria.isNegated()?CompareCriteria.NE:CompareCriteria.EQ, criteria.getRightExpression()));
}
}
}
return criteria;
}
private Criteria getSimpliedCriteria(Criteria crit, Expression a, boolean outcome, boolean nullPossible) {
if (nullPossible) {
if (outcome) {
if (processing) {
return crit;
}
IsNullCriteria inc = new IsNullCriteria(a);
inc.setNegated(true);
return inc;
}
} else if (outcome) {
return TRUE_CRITERIA;
}
return FALSE_CRITERIA;
}
private boolean rewriteLeftExpression(AbstractSetCriteria criteria) throws TeiidComponentException, TeiidProcessingException{
criteria.setExpression(rewriteExpressionDirect(criteria.getExpression()));
if (isNull(criteria.getExpression())) {
return true;
}
return false;
}
private Criteria rewriteCriteria(SetCriteria criteria) throws TeiidComponentException, TeiidProcessingException{
if (criteria.isAllConstants() && criteria.getValues().size() > 1 && criteria.getExpression() instanceof ElementSymbol) {
return criteria;
}
criteria.setExpression(rewriteExpressionDirect(criteria.getExpression()));
if (rewriteLeftExpression(criteria) && !criteria.getValues().isEmpty()) {
return UNKNOWN_CRITERIA;
}
Collection vals = criteria.getValues();
LinkedHashSet newVals = new LinkedHashSet(vals.size());
Iterator valIter = vals.iterator();
boolean allConstants = true;
boolean hasNull = false;
while(valIter.hasNext()) {
Expression value = rewriteExpressionDirect( (Expression) valIter.next());
if (isNull(value)) {
hasNull = true;
if (!preserveUnknown) {
if (criteria.isNegated()) {
return FALSE_CRITERIA;
}
continue;
}
}
allConstants &= value instanceof Constant;
newVals.add(value);
}
int size = newVals.size();
if (size == 1) {
if (preserveUnknown && hasNull) {
return UNKNOWN_CRITERIA;
}
Expression value = (Expression)newVals.iterator().next();
return rewriteCriteria(new CompareCriteria(criteria.getExpression(), criteria.isNegated()?CompareCriteria.NE:CompareCriteria.EQ, value));
}
criteria.setValues(newVals);
if (allConstants) {
criteria.setAllConstants(true);
if (!DataTypeManager.isHashable(criteria.getExpression().getType())) {
criteria.setValues(new TreeSet(criteria.getValues()));
}
}
if (size == 0) {
if (hasNull) {
return UNKNOWN_CRITERIA;
}
return criteria.isNegated()?TRUE_CRITERIA:FALSE_CRITERIA;
}
if(criteria.getExpression() instanceof Function ) {
Function leftFunction = (Function)criteria.getExpression();
if(FunctionLibrary.isConvert(leftFunction)) {
return simplifyConvertFunction(criteria);
}
}
return criteria;
}
private Criteria rewriteCriteria(IsNullCriteria criteria) throws TeiidComponentException, TeiidProcessingException{
criteria.setExpression(rewriteExpressionDirect(criteria.getExpression()));
return criteria;
}
public static Expression rewriteExpression(Expression expression, CommandContext context, QueryMetadataInterface metadata) throws TeiidComponentException, TeiidProcessingException{
return rewriteExpression(expression, context, metadata, false);
}
public static Expression rewriteExpression(Expression expression, CommandContext context, QueryMetadataInterface metadata, boolean rewriteSubcommands) throws TeiidComponentException, TeiidProcessingException{
QueryRewriter rewriter = new QueryRewriter(metadata, context);
rewriter.rewriteSubcommands = rewriteSubcommands;
return rewriter.rewriteExpressionDirect(expression);
}
private Expression rewriteExpressionDirect(Expression expression) throws TeiidComponentException, TeiidProcessingException{
if (expression instanceof Constant) {
return expression;
}
if (expression instanceof ElementSymbol) {
ElementSymbol es = (ElementSymbol)expression;
Class<?> type = es.getType();
if (!processing && es.isExternalReference()) {
if (variables == null) {
return new Reference(es);
}
Expression value = variables.get(es);
if (value == null) {
if (es.getGroupSymbol().getSchema() == null) {
String grpName = es.getGroupSymbol().getName();
if (grpName.equals(ProcedureReservedWords.CHANGING)) {
Assertion.failed("Changing value should not be null"); //$NON-NLS-1$
}
}
} else if (value instanceof Constant) {
if (value.getType() == type) {
return value;
}
try {
return new Constant(FunctionMethods.convert(context, ((Constant)value).getValue(), DataTypeManager.getDataTypeName(type)), es.getType());
} catch (FunctionExecutionException e) {
throw new QueryValidatorException(e);
}
}
return new Reference(es);
}
return expression;
}
boolean isBindEligible = true;
if (expression instanceof AggregateSymbol) {
expression = rewriteExpression((AggregateSymbol)expression);
} else if(expression instanceof Function) {
isBindEligible = !isConstantConvert(expression);
expression = rewriteFunction((Function) expression);
} else if (expression instanceof CaseExpression) {
expression = rewriteCaseExpression((CaseExpression)expression);
} else if (expression instanceof SearchedCaseExpression) {
expression = rewriteCaseExpression((SearchedCaseExpression)expression);
} else if (expression instanceof ScalarSubquery) {
ScalarSubquery subquery = (ScalarSubquery)expression;
if (subquery.shouldEvaluate() && processing) {
return new Constant(evaluator.evaluate(subquery, null), subquery.getType());
}
rewriteSubqueryContainer(subquery, true);
if (!RelationalNodeUtil.shouldExecute(subquery.getCommand(), false, true)) {
return new Constant(null, subquery.getType());
}
if (subquery.getCommand().getProcessorPlan() == null) {
addImplicitLimit(subquery, 2);
}
return expression;
} else if (expression instanceof ExpressionSymbol) {
expression = rewriteExpressionDirect(((ExpressionSymbol)expression).getExpression());
} else if (expression instanceof Criteria) {
expression = rewriteCriteria((Criteria)expression);
} else if (expression instanceof XMLSerialize) {
rewriteExpressions(expression);
XMLSerialize serialize = (XMLSerialize)expression;
if (isNull(serialize.getExpression())) {
return new Constant(null, serialize.getType());
}
if (serialize.getDeclaration() == null && serialize.isDocument()) {
if ((serialize.getVersion() != null && !serialize.getVersion().equals("1.0"))) { //$NON-NLS-1$
serialize.setDeclaration(true);
} else if (serialize.getEncoding() != null) {
Charset encoding = Charset.forName(serialize.getEncoding());
if (!encoding.equals(Charset.forName("UTF-8")) && !encoding.equals(Charset.forName("UTF-16"))) { //$NON-NLS-1$ //$NON-NLS-2$
serialize.setDeclaration(true);
}
}
}
} else if (expression instanceof XMLCast) {
XMLCast cast = (XMLCast)expression;
if (cast.getType() == DefaultDataClasses.XML) {
XMLQuery xmlQuery = new XMLQuery();
xmlQuery.setXquery("$i"); //$NON-NLS-1$
xmlQuery.setPassing(Arrays.asList(new DerivedColumn("i", cast.getExpression()))); //$NON-NLS-1$
xmlQuery.compileXqueryExpression();
return xmlQuery;
}
} else {
rewriteExpressions(expression);
}
if(!processing) {
if (!EvaluatableVisitor.isFullyEvaluatable(expression, true)) {
return expression;
}
} else if (!(expression instanceof Reference) && !EvaluatableVisitor.isEvaluatable(expression, EvaluationLevel.PROCESSING)) {
return expression;
}
return evaluate(expression, isBindEligible);
}
private Constant evaluate(Expression expression, boolean isBindEligible)
throws ExpressionEvaluationException, BlockedException,
TeiidComponentException {
Object value = null;
if (expression instanceof Criteria) {
value = evaluator.evaluateTVL((Criteria)expression, Collections.emptyList());
} else {
value = evaluator.evaluate(expression, Collections.emptyList());
}
if (value instanceof Constant) {
return (Constant)value; //multi valued substitution
}
Constant result = new Constant(value, expression.getType());
result.setBindEligible(isBindEligible);
return result;
}
private boolean isConstantConvert(Expression ex) {
if (ex instanceof Constant) {
return true;
}
if (!(ex instanceof Function)) {
return false;
}
Function f = (Function)ex;
if (!FunctionLibrary.isConvert(f)) {
return false;
}
return isConstantConvert(f.getArg(0));
}
private Expression rewriteExpression(AggregateSymbol expression) throws TeiidComponentException, TeiidProcessingException {
if (expression.isBoolean()) {
if (expression.getAggregateFunction() == Type.EVERY) {
expression.setAggregateFunction(Type.MIN);
} else {
expression.setAggregateFunction(Type.MAX);
}
}
if ((expression.getAggregateFunction() == Type.MAX || expression.getAggregateFunction() == Type.MIN)) {
if (expression.isDistinct()) {
expression.setDistinct(false);
}
if (rewriteAggs && expression.getArg(0) != null && EvaluatableVisitor.willBecomeConstant(expression.getArg(0))) {
return expression.getArg(0);
}
}
if (expression.isDistinct() && expression.getAggregateFunction() == Type.USER_DEFINED && expression.getFunctionDescriptor().getMethod().getAggregateAttributes().usesDistinctRows()) {
expression.setDistinct(false);
}
Expression[] args = expression.getArgs();
if (args.length == 1 && expression.getCondition() != null && !expression.respectsNulls()) {
Expression cond = expression.getCondition();
Expression ex = expression.getArg(0);
if (!(cond instanceof Criteria)) {
cond = new ExpressionCriteria(cond);
}
SearchedCaseExpression sce = new SearchedCaseExpression(Arrays.asList(cond), Arrays.asList(ex));
sce.setType(ex.getType());
expression.setCondition(null);
expression.setArgs(new Expression[] {sce});
args = expression.getArgs();
}
for (int i = 0; i < args.length; i++) {
args[i] = rewriteExpressionDirect(expression.getArg(i));
}
return expression;
}
private static Map<String, Integer> FUNCTION_MAP = new TreeMap<String, Integer>(String.CASE_INSENSITIVE_ORDER);
static {
FUNCTION_MAP.put(FunctionLibrary.SPACE, 0);
FUNCTION_MAP.put(FunctionLibrary.FROM_UNIXTIME, 1);
FUNCTION_MAP.put(FunctionLibrary.NULLIF, 2);
FUNCTION_MAP.put(FunctionLibrary.COALESCE, 3);
FUNCTION_MAP.put(FunctionLibrary.CONCAT2, 4);
FUNCTION_MAP.put(FunctionLibrary.TIMESTAMPADD, 5);
FUNCTION_MAP.put(FunctionLibrary.PARSEDATE, 6);
FUNCTION_MAP.put(FunctionLibrary.PARSETIME, 7);
FUNCTION_MAP.put(FunctionLibrary.FORMATDATE, 8);
FUNCTION_MAP.put(FunctionLibrary.FORMATTIME, 9);
FUNCTION_MAP.put(SourceSystemFunctions.TRIM, 10);
FUNCTION_MAP.put(SourceSystemFunctions.SUBSTRING, 11);
}
private Expression rewriteFunction(Function function) throws TeiidComponentException, TeiidProcessingException{
//rewrite alias functions
String functionName = function.getName();
String actualName =ALIASED_FUNCTIONS.get(functionName);
FunctionLibrary funcLibrary = this.metadata.getFunctionLibrary();
if (actualName != null) {
function.setName(actualName);
Expression[] args = function.getArgs();
Class<?>[] types = new Class[args.length];
for(int i=0; i<args.length; i++) {
types[i] = args[i].getType();
}
FunctionDescriptor descriptor = funcLibrary.findFunction(actualName, types);
function.setFunctionDescriptor(descriptor);
}
if(StringUtil.startsWithIgnoreCase(functionName, "parse")) { //$NON-NLS-1$
String type = functionName.substring(5);
if (PARSE_FORMAT_TYPES.contains(type) && Number.class.isAssignableFrom(function.getType()) && !type.equals(DataTypeManager.DefaultDataTypes.BIG_DECIMAL)) {
Function result = new Function(SourceSystemFunctions.PARSEBIGDECIMAL, function.getArgs());
FunctionDescriptor descriptor =
funcLibrary.findFunction(SourceSystemFunctions.PARSEBIGDECIMAL, new Class[] { DataTypeManager.DefaultDataClasses.STRING, DataTypeManager.DefaultDataClasses.STRING });
result.setFunctionDescriptor(descriptor);
result.setType(DataTypeManager.DefaultDataClasses.BIG_DECIMAL);
return rewriteFunction(ResolverUtil.getConversion(result, DataTypeManager.DefaultDataTypes.BIG_DECIMAL, DataTypeManager.getDataTypeName(function.getType()), false, metadata.getFunctionLibrary()));
} else if ((DataTypeManager.DefaultDataTypes.DATE.equalsIgnoreCase(type)
|| DataTypeManager.DefaultDataTypes.TIME.equalsIgnoreCase(type)) && function.getArg(1) instanceof Constant) {
String format = "yyyy-MM-dd"; //$NON-NLS-1$
int length = 10;
if (DataTypeManager.DefaultDataTypes.TIME.equalsIgnoreCase(type)) {
format = "hh:mm:ss"; //$NON-NLS-1$
length = 8;
}
Constant c = (Constant) function.getArg(1);
if (format.equals(c.getValue())) {
Expression arg = function.getArg(0);
if ((arg instanceof Function)
&& FunctionLibrary.isConvert((Function)arg)
&& java.util.Date.class.isAssignableFrom(((Function)arg).getArg(0).getType())) {
return rewriteExpressionDirect(ResolverUtil.getConversion(arg, DataTypeManager.DefaultDataTypes.STRING, type, false, metadata.getFunctionLibrary()));
}
}
}
} else if(StringUtil.startsWithIgnoreCase(functionName, "format")) { //$NON-NLS-1$
String type = functionName.substring(6);
if (PARSE_FORMAT_TYPES.contains(type) && Number.class.isAssignableFrom(function.getArg(0).getType()) && !type.equals(DataTypeManager.DefaultDataTypes.BIG_DECIMAL)) {
Function bigDecimalParam = ResolverUtil.getConversion(function.getArg(0), DataTypeManager.getDataTypeName(function.getArg(0).getType()), DataTypeManager.DefaultDataTypes.BIG_DECIMAL, false, metadata.getFunctionLibrary());
Function result = new Function(SourceSystemFunctions.FORMATBIGDECIMAL, new Expression[] {bigDecimalParam, function.getArg(1)});
FunctionDescriptor descriptor =
funcLibrary.findFunction(SourceSystemFunctions.FORMATBIGDECIMAL, new Class[] { DataTypeManager.DefaultDataClasses.BIG_DECIMAL, DataTypeManager.DefaultDataClasses.STRING });
result.setFunctionDescriptor(descriptor);
result.setType(DataTypeManager.DefaultDataClasses.STRING);
return rewriteFunction(result);
} else if ((DataTypeManager.DefaultDataTypes.DATE.equalsIgnoreCase(type)
|| DataTypeManager.DefaultDataTypes.TIME.equalsIgnoreCase(type)) && function.getArg(1) instanceof Constant) {
String format = "yyyy-MM-dd"; //$NON-NLS-1$
if (DataTypeManager.DefaultDataTypes.TIME.equalsIgnoreCase(type)) {
format = "hh:mm:ss"; //$NON-NLS-1$
}
Constant c = (Constant) function.getArg(1);
if (format.equals(c.getValue())) {
return rewriteExpressionDirect(ResolverUtil.getConversion(function.getArg(0), DataTypeManager.getDataTypeName(function.getArg(0).getType()), DataTypeManager.DefaultDataTypes.STRING, false, metadata.getFunctionLibrary()));
}
}
}
boolean omitNull = false;
Integer code = FUNCTION_MAP.get(functionName);
if (code != null) {
switch (code) {
case 0: { //space(x) => repeat(' ', x)
Function result = new Function(SourceSystemFunctions.REPEAT,
new Expression[] {new Constant(" "), function.getArg(0)}); //$NON-NLS-1$
//resolve the function
FunctionDescriptor descriptor =
funcLibrary.findFunction(SourceSystemFunctions.REPEAT, new Class[] { DataTypeManager.DefaultDataClasses.STRING, DataTypeManager.DefaultDataClasses.INTEGER});
result.setFunctionDescriptor(descriptor);
result.setType(DataTypeManager.DefaultDataClasses.STRING);
function = result;
break;
}
case 1: {
// TEIID-4455
break;
}
case 2: { //rewrite nullif(a, b) => case when (a = b) then null else a
List when = Arrays.asList(new Criteria[] {new CompareCriteria(function.getArg(0), CompareCriteria.EQ, function.getArg(1))});
Constant nullConstant = new Constant(null, function.getType());
List then = Arrays.asList(new Expression[] {nullConstant});
SearchedCaseExpression caseExpr = new SearchedCaseExpression(when, then);
caseExpr.setElseExpression(function.getArg(0));
caseExpr.setType(function.getType());
return rewriteExpressionDirect(caseExpr);
}
case 3: {
Expression[] args = function.getArgs();
if (args.length == 2) {
Function result = new Function(SourceSystemFunctions.IFNULL,
new Expression[] {function.getArg(0), function.getArg(1) });
//resolve the function
FunctionDescriptor descriptor =
funcLibrary.findFunction(SourceSystemFunctions.IFNULL, new Class[] { function.getType(), function.getType() });
result.setFunctionDescriptor(descriptor);
result.setType(function.getType());
function = result;
}
break;
}
case 4:
omitNull = true;
break;
case 5: {
if (function.getType() != DataTypeManager.DefaultDataClasses.TIMESTAMP) {
FunctionDescriptor descriptor =
funcLibrary.findFunction(SourceSystemFunctions.TIMESTAMPADD, new Class[] { DataTypeManager.DefaultDataClasses.STRING, DataTypeManager.DefaultDataClasses.INTEGER, DataTypeManager.DefaultDataClasses.TIMESTAMP });
function.setFunctionDescriptor(descriptor);
Class<?> type = function.getType();
function.setType(DataTypeManager.DefaultDataClasses.TIMESTAMP);
function.getArgs()[2] = ResolverUtil.getConversion(function.getArg(2), DataTypeManager.getDataTypeName(type), DataTypeManager.DefaultDataTypes.TIMESTAMP, false, funcLibrary);
function = ResolverUtil.getConversion(function, DataTypeManager.DefaultDataTypes.TIMESTAMP, DataTypeManager.getDataTypeName(type), false, funcLibrary);
}
break;
}
case 6:
case 7: {
FunctionDescriptor descriptor =
funcLibrary.findFunction(SourceSystemFunctions.PARSETIMESTAMP, new Class[] { DataTypeManager.DefaultDataClasses.STRING, DataTypeManager.DefaultDataClasses.STRING });
function.setName(SourceSystemFunctions.PARSETIMESTAMP);
function.setFunctionDescriptor(descriptor);
Class<?> type = function.getType();
function.setType(DataTypeManager.DefaultDataClasses.TIMESTAMP);
function = ResolverUtil.getConversion(function, DataTypeManager.DefaultDataTypes.TIMESTAMP, DataTypeManager.getDataTypeName(type), false, funcLibrary);
break;
}
case 8:
case 9: {
FunctionDescriptor descriptor =
funcLibrary.findFunction(SourceSystemFunctions.FORMATTIMESTAMP, new Class[] { DataTypeManager.DefaultDataClasses.TIMESTAMP, DataTypeManager.DefaultDataClasses.STRING });
function.setName(SourceSystemFunctions.FORMATTIMESTAMP);
function.setFunctionDescriptor(descriptor);
function.getArgs()[0] = ResolverUtil.getConversion(function.getArg(0), DataTypeManager.getDataTypeName(function.getArg(0).getType()), DataTypeManager.DefaultDataTypes.TIMESTAMP, false, funcLibrary);
break;
}
case 10: {
if (new Constant(" ").equals(function.getArg(1))) { //$NON-NLS-1$
String spec = (String)((Constant)function.getArg(0)).getValue();
Expression string = function.getArg(2);
if (!SQLConstants.Reserved.TRAILING.equalsIgnoreCase(spec)) {
function = new Function(SourceSystemFunctions.LTRIM, new Expression[] {string});
FunctionDescriptor descriptor = funcLibrary.findFunction(SourceSystemFunctions.LTRIM, new Class[] { DataTypeManager.DefaultDataClasses.STRING });
function.setFunctionDescriptor(descriptor);
function.setType(DataTypeManager.DefaultDataClasses.STRING);
string = function;
}
if (!SQLConstants.Reserved.LEADING.equalsIgnoreCase(spec)) {
function = new Function(SourceSystemFunctions.RTRIM, new Expression[] {string});
FunctionDescriptor descriptor = funcLibrary.findFunction(SourceSystemFunctions.RTRIM, new Class[] { DataTypeManager.DefaultDataClasses.STRING });
function.setFunctionDescriptor(descriptor);
function.setType(DataTypeManager.DefaultDataClasses.STRING);
}
}
break;
}
case 11: {
if (function.getArg(1) instanceof Constant) {
Constant c = (Constant)function.getArg(1);
if (!c.isMultiValued() && !c.isNull()) {
int val = (Integer) c.getValue();
if (val == 0) {
function.getArgs()[1] = new Constant(1);
} else if (val < 0) {
return new Constant(null, function.getType());
}
}
}
break;
}
}
}
Expression[] args = function.getArgs();
Expression[] newArgs = new Expression[args.length];
// Rewrite args
int j = 0;
for(int i=0; i<args.length; i++) {
Expression ex = rewriteExpressionDirect(args[i]);
if (isNull(ex)) {
if (!function.getFunctionDescriptor().isNullDependent()) {
return new Constant(null, function.getType());
}
if (omitNull) {
continue;
}
}
newArgs[j++] = ex;
}
if (omitNull) {
if (j==0) {
return new Constant(null, function.getType());
}
if (j==1) {
return newArgs[0];
}
if (j!=args.length) {
newArgs = Arrays.copyOf(newArgs, j);
}
}
function.setArgs(newArgs);
if( FunctionLibrary.isConvert(function)) {
Class<?> srcType = newArgs[0].getType();
Class<?> tgtType = function.getType();
if(srcType != null && tgtType != null && srcType.equals(tgtType)) {
return newArgs[0]; //unnecessary conversion
}
if (function.isImplicit()) {
function.setImplicit(false);
}
if (!(newArgs[0] instanceof Function) || tgtType == DataTypeManager.DefaultDataClasses.OBJECT) {
return function;
}
Function nested = (Function) newArgs[0];
if (!FunctionLibrary.isConvert(nested)) {
return function;
}
Class<?> nestedType = nested.getArgs()[0].getType();
Transform t = DataTypeManager.getTransform(nestedType, nested.getType());
if (t.isExplicit()) {
//explicit conversions are required
return function;
}
if (DataTypeManager.getTransform(nestedType, tgtType) == null) {
//no direct conversion exists
return function;
}
//can't remove a convert that would alter the lexical form
if (tgtType == DataTypeManager.DefaultDataClasses.STRING &&
(nestedType == DataTypeManager.DefaultDataClasses.BOOLEAN
|| nestedType == DataTypeManager.DefaultDataClasses.DATE
|| nestedType == DataTypeManager.DefaultDataClasses.TIME
|| tgtType == DataTypeManager.DefaultDataClasses.BIG_DECIMAL
|| tgtType == DataTypeManager.DefaultDataClasses.FLOAT
|| (tgtType == DataTypeManager.DefaultDataClasses.DOUBLE && srcType != DataTypeManager.DefaultDataClasses.FLOAT))) {
return function;
}
//nested implicit transform is not needed
return rewriteExpressionDirect(ResolverUtil.getConversion(nested.getArgs()[0], DataTypeManager.getDataTypeName(nestedType), DataTypeManager.getDataTypeName(tgtType), false, funcLibrary));
}
//convert DECODESTRING function to CASE expression
if( function.getName().equalsIgnoreCase(FunctionLibrary.DECODESTRING)
|| function.getName().equalsIgnoreCase(FunctionLibrary.DECODEINTEGER)) {
return convertDecodeFunction(function);
}
return function;
}
private Expression convertDecodeFunction(Function function){
Expression exprs[] = function.getArgs();
String decodeString = (String)((Constant)exprs[1]).getValue();
String decodeDelimiter = ","; //$NON-NLS-1$
if(exprs.length == 3){
decodeDelimiter = (String)((Constant)exprs[2]).getValue();
}
List<Criteria> newWhens = new ArrayList<Criteria>();
List<Constant> newThens = new ArrayList<Constant>();
Constant elseConst = null;
StringTokenizer tokenizer = new StringTokenizer(decodeString, decodeDelimiter);
while (tokenizer.hasMoreTokens()) {
String resultString;
String compareString =
convertString(tokenizer.nextToken().trim());
if (tokenizer.hasMoreTokens()) {
resultString = convertString(tokenizer.nextToken().trim());
Criteria crit;
if (compareString == null) {
crit = new IsNullCriteria((Expression) exprs[0].clone());
} else {
crit = new CompareCriteria((Expression) exprs[0].clone(), CompareCriteria.EQ, new Constant(compareString));
}
newWhens.add(crit);
newThens.add(new Constant(resultString));
}else {
elseConst = new Constant(compareString);
}
}
SearchedCaseExpression newCaseExpr = new SearchedCaseExpression(newWhens, newThens);
if(elseConst != null) {
newCaseExpr.setElseExpression(elseConst);
}else {
newCaseExpr.setElseExpression(exprs[0]);
}
newCaseExpr.setType(DefaultDataClasses.STRING);
if (function.getName().equalsIgnoreCase(FunctionLibrary.DECODEINTEGER)) {
return ResolverUtil.getConversion(newCaseExpr, DataTypeManager.DefaultDataTypes.STRING, DataTypeManager.DefaultDataTypes.INTEGER, false, metadata.getFunctionLibrary());
}
return newCaseExpr;
}
private static String convertString(String string) {
/*
* if there are no characters in the compare string we designate that as
* an indication of null. ie if the decode string looks like this:
*
* "'this', 1,,'null'"
*
* Then if the value in the first argument is null then the String 'null' is
* returned from the function.
*/
if (string.equals("")) { //$NON-NLS-1$
return null;
}
/*
* we also allow the use of the keyword null in the decode string. if it
* wished to match on the string 'null' then the string must be qualified by
* ' designators.
*/
if(string.equalsIgnoreCase("null")){ //$NON-NLS-1$
return null;
}
/*
* Here we check to see if the String in the decode String submitted
* was surrounded by String literal characters. In this case we strip
* these literal characters from the String.
*/
if ((string.startsWith("\"") && string.endsWith("\"")) //$NON-NLS-1$ //$NON-NLS-2$
|| (string.startsWith("'") && string.endsWith("'"))) { //$NON-NLS-1$ //$NON-NLS-2$
if (string.length() == 2) {
/*
* This is an indication that the desired string to be compared is
* the "" empty string, so we return it as such.
*/
string = ""; //$NON-NLS-1$
} else if (!string.equalsIgnoreCase("'") && !string.equalsIgnoreCase("\"")){ //$NON-NLS-1$ //$NON-NLS-2$
string = string.substring(1);
string = string.substring(0, string.length()-1);
}
}
return string;
}
private Expression rewriteCaseExpression(CaseExpression expr)
throws TeiidComponentException, TeiidProcessingException{
List<CompareCriteria> whens = new ArrayList<CompareCriteria>(expr.getWhenCount());
for (Expression expression: (List<Expression>)expr.getWhen()) {
whens.add(new CompareCriteria((Expression)expr.getExpression().clone(), CompareCriteria.EQ, expression));
}
SearchedCaseExpression sce = new SearchedCaseExpression(whens, expr.getThen());
sce.setElseExpression(expr.getElseExpression());
sce.setType(expr.getType());
return rewriteCaseExpression(sce);
}
private Expression rewriteCaseExpression(SearchedCaseExpression expr)
throws TeiidComponentException, TeiidProcessingException{
int whenCount = expr.getWhenCount();
ArrayList<Criteria> whens = new ArrayList<Criteria>(whenCount);
ArrayList<Expression> thens = new ArrayList<Expression>(whenCount);
boolean hasTrue = false;
for (int i = 0; i < whenCount; i++) {
// Check the when to see if this CASE can be rewritten due to an always true/false when
Criteria rewrittenWhen = rewriteCriteria(expr.getWhenCriteria(i));
if (rewrittenWhen == FALSE_CRITERIA || rewrittenWhen == UNKNOWN_CRITERIA) {
continue;
}
whens.add(rewrittenWhen);
thens.add(rewriteExpressionDirect(expr.getThenExpression(i)));
if(rewrittenWhen == TRUE_CRITERIA) {
if (i == 0) {
// WHEN is always true, so just return the THEN
return rewriteExpressionDirect(expr.getThenExpression(i));
}
hasTrue = true;
break;
}
}
if (expr.getElseExpression() != null) {
if (!hasTrue) {
expr.setElseExpression(rewriteExpressionDirect(expr.getElseExpression()));
} else {
expr.setElseExpression(null);
}
}
Expression elseExpr = expr.getElseExpression();
if(whens.size() == 0) {
// No WHENs left, so just return the ELSE
if(elseExpr == null) {
// No else, no valid whens, just return null constant typed same as CASE
return new Constant(null, expr.getType());
}
// Rewrite the else and return
return elseExpr;
}
expr.setWhen(whens, thens);
/* optimization for case 5413:
* If all of the remaining 'thens' and the 'else' evaluate to the same value,
* just return the 'else' expression.
*/
if ( elseExpr != null ) {
boolean bAllMatch = true;
for (int i = 0; i < whenCount; i++) {
if ( !thens.get( i ).equals(elseExpr) ) {
bAllMatch = false;
break;
}
}
if ( bAllMatch ) {
return elseExpr;
}
}
return expr;
}
private Command rewriteExec(StoredProcedure storedProcedure) throws TeiidComponentException, TeiidProcessingException{
//After this method, no longer need to display named parameters
storedProcedure.setDisplayNamedParameters(false);
for (SPParameter param : storedProcedure.getInputParameters()) {
if (!processing || storedProcedure.isPushedInQuery()) {
param.setExpression(rewriteExpressionDirect(param.getExpression()));
} else if (!(param.getExpression() instanceof Constant)) {
boolean isBindEligible = !isConstantConvert(param.getExpression());
param.setExpression(evaluate(param.getExpression(), isBindEligible));
}
}
return storedProcedure;
}
private Command rewriteInsert(Insert insert) throws TeiidComponentException, TeiidProcessingException{
Command c = rewriteInsertForWriteThrough(insert);
if (c != null) {
return c;
}
UpdateInfo info = insert.getUpdateInfo();
if (info != null && info.isInherentInsert()) {
//TODO: update error messages
UpdateMapping mapping = info.findInsertUpdateMapping(insert, true);
if (mapping == null) {
throw new QueryValidatorException(QueryPlugin.Event.TEIID30375, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30375, insert.getVariables()));
}
Map<ElementSymbol, ElementSymbol> symbolMap = mapping.getUpdatableViewSymbols();
List<ElementSymbol> mappedSymbols = new ArrayList<ElementSymbol>(insert.getVariables().size());
for (ElementSymbol symbol : insert.getVariables()) {
mappedSymbols.add(symbolMap.get(symbol));
}
insert.setVariables(mappedSymbols);
insert.setGroup(mapping.getGroup().clone());
insert.setUpdateInfo(ProcedureContainerResolver.getUpdateInfo(insert.getGroup(), metadata, Command.TYPE_INSERT, true));
return rewriteInsert(insert);
}
if ( insert.getQueryExpression() != null ) {
insert.setQueryExpression((QueryCommand)rewriteCommand(insert.getQueryExpression(), true));
return correctDatatypes(insert);
}
// Evaluate any function / constant trees in the insert values
List expressions = insert.getValues();
List evalExpressions = new ArrayList(expressions.size());
Iterator expIter = expressions.iterator();
boolean preserveUnknownOld = preserveUnknown;
preserveUnknown = true;
while(expIter.hasNext()) {
Expression exp = (Expression) expIter.next();
if (processing && exp instanceof ExpressionSymbol) {
//expression symbols that were created in the PlanToProcessesConverter
evalExpressions.add( evaluate(exp, true));
} else {
evalExpressions.add( rewriteExpressionDirect( exp ));
}
}
preserveUnknown = preserveUnknownOld;
insert.setValues(evalExpressions);
return insert;
}
private Command rewriteInsertForWriteThrough(Insert insert)
throws TeiidComponentException, QueryMetadataException,
QueryResolverException, TeiidProcessingException {
if (processing
|| insert.hasTag(WRITE_THROUGH)
|| !metadata.hasMaterialization(insert.getGroup().getMetadataID())
|| !Boolean.valueOf(metadata.getExtensionProperty(insert.getGroup().getMetadataID(), MaterializationMetadataRepository.MATVIEW_WRITE_THROUGH, false))) {
return null;
}
//create a block to save the insert values, then insert them into both the view and the materialization
//it would be better to combine this with project into, but there are several paths we have to account for:
//- upserts
//- insert rewritten via inherent logic
//- insert with a single set of values (non-deterministic requires creating a single value set)
//- insert with a query expression
//so instead we'll just do this in a single place for now
Block block = new Block();
block.setAtomic(true);
Insert cloneInsert = (Insert)insert.clone();
Insert values = new Insert();
GroupSymbol temp = new GroupSymbol("#temp"); //$NON-NLS-1$
if (context.getGroups().contains(temp.getName())) {
temp = RulePlaceAccess.recontextSymbol(temp, context.getGroups());
temp.setDefinition(null);
}
values.setGroup(temp);
if (insert.getQueryExpression() != null) {
values.setQueryExpression(insert.getQueryExpression());
} else {
values.setValues(insert.getValues());
}
for (ElementSymbol es : insert.getVariables()) {
values.addVariable(new ElementSymbol(es.getShortName()));
}
block.addStatement(new CommandStatement(values));
Query q = new Query();
q.setSelect(new Select(Arrays.asList(new MultipleElementSymbol())));
q.setFrom(new From(Arrays.asList(new UnaryFromClause(temp))));
insert.setQueryExpression((QueryCommand) q.clone());
insert.addTag(WRITE_THROUGH);
block.addStatement(new CommandStatement(insert));
ElementSymbol rowCount = new ElementSymbol(ProcedureReservedWords.ROWCOUNT);
ElementSymbol val = new ElementSymbol("val"); //$NON-NLS-1$
DeclareStatement ds = new DeclareStatement(val, DataTypeManager.DefaultDataTypes.INTEGER, rowCount);
block.addStatement(ds);
Object target = metadata.getMaterialization(insert.getGroup().getMetadataID());
if (target != null) {
GroupSymbol newGroup = new GroupSymbol(metadata.getFullName(target));
newGroup.setMetadataID(target);
List<ElementSymbol> newVars = new ArrayList<ElementSymbol>();
for (ElementSymbol es : insert.getVariables()) {
ElementSymbol newVariable = new ElementSymbol(es.getShortName(), newGroup.clone());
newVars.add(newVariable);
}
cloneInsert.setVariables(newVars);
cloneInsert.setGroup(newGroup);
cloneInsert.setUpdateInfo(null);
cloneInsert.getValues().clear();
cloneInsert.setQueryExpression(q);
block.addStatement(new CommandStatement(cloneInsert));
} else {
Object key = NewCalculateCostUtil.getKeyUsed(insert.getVariables(), Collections.singleton(insert.getGroup()), metadata, true);
if (key == null || key != metadata.getPrimaryKey(insert.getGroup().getMetadataID())) {
//we need a key for this logic to work
return null;
}
Block b = new Block();
LoopStatement loop = new LoopStatement(b, (Query)q.clone(), "x"); //$NON-NLS-1$
StoredProcedure sp = new StoredProcedure();
sp.setProcedureName("SYSAdmin.refreshMatViewRow"); //$NON-NLS-1$
sp.setParameter(new SPParameter(1, new Constant(metadata.getFullName(insert.getGroup().getMetadataID()))));
List<Object> ids = metadata.getElementIDsInKey(key);
int index = 2;
for (Object id : ids) {
sp.setParameter(new SPParameter(index++, new ElementSymbol(metadata.getName(id))));
}
b.addStatement(new CommandStatement(sp));
block.addStatement(loop);
}
Query result = new Query();
result.setSelect(new Select(Arrays.asList(val)));
block.addStatement(new CommandStatement(result));
CreateProcedureCommand command = new CreateProcedureCommand(block);
QueryResolver.resolveCommand(command, metadata);
return rewriteCommand(command, false);
}
private Command rewriteForWriteThrough(ProcedureContainer update)
throws TeiidComponentException, QueryMetadataException,
QueryResolverException, TeiidProcessingException {
if (processing
|| update.hasTag(WRITE_THROUGH)
|| !metadata.hasMaterialization(update.getGroup().getMetadataID())
|| !Boolean.valueOf(metadata.getExtensionProperty(update.getGroup().getMetadataID(), MaterializationMetadataRepository.MATVIEW_WRITE_THROUGH, false))) {
return null;
}
//internal
//update view ... where predicate - mark as write through
//loop on (select pk from view where predicate) - mark as write through, it's ok that this will use cache
// refreshMatView ...
//end
//external
//update view ... where predicate - mark as write through
//update target ... where predicate - mark as write through
Block block = new Block();
block.setAtomic(true);
ProcedureContainer clone = (ProcedureContainer)update.clone();
GroupSymbol temp = new GroupSymbol("#temp"); //$NON-NLS-1$
if (context.getGroups().contains(temp.getName())) {
temp = RulePlaceAccess.recontextSymbol(temp, context.getGroups());
temp.setDefinition(null);
}
Query q = new Query();
q.setSelect(new Select(Arrays.asList(new MultipleElementSymbol())));
q.setFrom(new From(Arrays.asList(new UnaryFromClause(temp))));
update.addTag(WRITE_THROUGH);
block.addStatement(new CommandStatement(update));
ElementSymbol rowCount = new ElementSymbol(ProcedureReservedWords.ROWCOUNT);
ElementSymbol val = new ElementSymbol("val"); //$NON-NLS-1$
DeclareStatement ds = new DeclareStatement(val, DataTypeManager.DefaultDataTypes.INTEGER, rowCount);
block.addStatement(ds);
final Object gid = update.getGroup().getMetadataID();
Object target = metadata.getMaterialization(gid);
if (target != null) {
final GroupSymbol newGroup = new GroupSymbol(metadata.getFullName(target));
newGroup.setMetadataID(target);
ExpressionMappingVisitor emv = new ExpressionMappingVisitor(null) {
public Expression replaceExpression(Expression element) {
if (element instanceof ElementSymbol) {
ElementSymbol es = (ElementSymbol)element;
if (es.getGroupSymbol().getMetadataID() == gid) {
es.setGroupSymbol(newGroup);
}
}
return element;
}
};
if (clone instanceof Update) {
Update u = (Update)clone;
for (SetClause sc : u.getChangeList().getClauses()) {
sc.setSymbol(new ElementSymbol(sc.getSymbol().getShortName(), newGroup.clone()));
PreOrPostOrderNavigator.doVisit(sc, emv, PreOrPostOrderNavigator.POST_ORDER);
}
u.setGroup(newGroup);
} else {
((Delete)clone).setGroup(newGroup);
}
PreOrPostOrderNavigator.doVisit(((FilteredCommand)clone).getCriteria(), emv, PreOrPostOrderNavigator.POST_ORDER);
clone.setUpdateInfo(null);
clone.addTag(WRITE_THROUGH);
block.addStatement(new CommandStatement(clone));
} else {
Object key = metadata.getPrimaryKey(update.getGroup().getMetadataID());
if (key == null) {
//we need a key for this logic to work
return null;
}
Block b = new Block();
List<Object> ids = metadata.getElementIDsInKey(key);
Select select = new Select();
for (Object id : ids) {
select.addSymbol(new ElementSymbol(metadata.getName(id)));
}
if (clone instanceof Update) {
Update u = (Update)clone;
for (SetClause sc : u.getChangeList().getClauses()) {
if (ids.contains(sc.getSymbol().getMetadataID())) {
//corner case of an update manipulating the primary key
return null;
}
}
}
Query keys = new Query();
keys.setSelect(select);
keys.setFrom(new From(Arrays.asList(new UnaryFromClause(update.getGroup()))));
if (((FilteredCommand)clone).getCriteria() != null) {
keys.setCriteria((Criteria) ((FilteredCommand)clone).getCriteria().clone());
}
LoopStatement loop = new LoopStatement(b, keys, "x"); //$NON-NLS-1$
StoredProcedure sp = new StoredProcedure();
sp.setProcedureName("SYSAdmin.refreshMatViewRow"); //$NON-NLS-1$
sp.setParameter(new SPParameter(1, new Constant(metadata.getFullName(update.getGroup().getMetadataID()))));
int index = 2;
for (Object id : ids) {
sp.setParameter(new SPParameter(index++, new ElementSymbol(metadata.getName(id))));
}
b.addStatement(new CommandStatement(sp));
block.addStatement(loop);
}
Query result = new Query();
result.setSelect(new Select(Arrays.asList(val)));
block.addStatement(new CommandStatement(result));
CreateProcedureCommand command = new CreateProcedureCommand(block);
QueryResolver.resolveCommand(command, metadata);
return rewriteCommand(command, false);
}
public static Command rewriteAsUpsertProcedure(Insert insert, QueryMetadataInterface metadata, CommandContext context)
throws TeiidComponentException, QueryMetadataException,
QueryValidatorException, QueryResolverException,
TeiidProcessingException {
QueryRewriter rewriter = new QueryRewriter(metadata, context);
Collection<?> keys = metadata.getUniqueKeysInGroup(insert.getGroup().getMetadataID());
if (keys.isEmpty()) {
throw new QueryValidatorException(QueryPlugin.Event.TEIID31132, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31132, insert.getGroup()));
}
Object key = keys.iterator().next();
Set<Object> keyCols = new LinkedHashSet<Object>(metadata.getElementIDsInKey(key));
Insert newInsert = new Insert();
newInsert.setGroup(insert.getGroup().clone());
newInsert.setVariables(LanguageObject.Util.deepClone(insert.getVariables(), ElementSymbol.class));
ArrayList<Expression> values = new ArrayList<Expression>();
IfStatement ifStatement = new IfStatement();
Query exists = new Query();
exists.setSelect(new Select(Arrays.asList(new Constant(1))));
exists.setFrom(new From(Arrays.asList(new UnaryFromClause(insert.getGroup().clone()))));
ifStatement.setCondition(new ExistsCriteria(exists));
Update update = new Update();
update.setGroup(insert.getGroup().clone());
SetClauseList setClauses = new SetClauseList();
update.setChangeList(setClauses);
List<Criteria> crits = new ArrayList<Criteria>();
GroupSymbol varGroup = getVarGroup(insert);
for (ElementSymbol es : insert.getVariables()) {
ElementSymbol var = new ElementSymbol(es.getShortName(), varGroup.clone());
values.add(var.clone());
if (keyCols.contains(es.getMetadataID())) {
CompareCriteria cc = new CompareCriteria(es.clone(), CompareCriteria.EQ, var.clone());
crits.add(cc);
} else {
setClauses.addClause(new SetClause(es.clone(), var.clone()));
}
}
newInsert.setValues(values);
update.setCriteria((Criteria) Criteria.combineCriteria(crits).clone());
exists.setCriteria((Criteria) Criteria.combineCriteria(crits).clone());
ifStatement.setIfBlock(new Block(new CommandStatement(update)));
ifStatement.setElseBlock(new Block(new CommandStatement(newInsert)));
//construct the value query
QueryCommand query = insert.getQueryExpression();
if (query == null) {
Query q = new Query();
Select s = new Select();
s.addSymbols(LanguageObject.Util.deepClone(insert.getValues(), Expression.class));
q.setSelect(s);
query = q;
}
query = createInlineViewQuery(new GroupSymbol("X"), query, metadata, insert.getVariables()); //$NON-NLS-1$
return rewriter.asLoopProcedure(insert.getGroup(), query, ifStatement, varGroup, Command.TYPE_INSERT);
}
private static GroupSymbol getVarGroup(TargetedCommand cmd) {
if (cmd.getGroup().getShortName().equalsIgnoreCase("X")) { //$NON-NLS-1$
return new GroupSymbol("X1"); //$NON-NLS-1$
}
return new GroupSymbol("X"); //$NON-NLS-1$
}
public static Query createInlineViewQuery(GroupSymbol inlineGroup,
Command nested,
QueryMetadataInterface metadata,
List<? extends Expression> actualSymbols) throws QueryMetadataException,
QueryResolverException,
TeiidComponentException {
Query query = new Query();
Select select = new Select();
query.setSelect(select);
From from = new From();
from.addClause(new UnaryFromClause(inlineGroup));
TempMetadataStore store = new TempMetadataStore();
TempMetadataAdapter tma = new TempMetadataAdapter(metadata, store);
if (nested instanceof QueryCommand) {
Query firstProject = ((QueryCommand)nested).getProjectedQuery();
makeSelectUnique(firstProject.getSelect(), false);
}
TempMetadataID gid = store.addTempGroup(inlineGroup.getName(), nested.getProjectedSymbols());
inlineGroup.setMetadataID(gid);
List<Class<?>> actualTypes = new ArrayList<Class<?>>(nested.getProjectedSymbols().size());
for (Expression ses : actualSymbols) {
actualTypes.add(ses.getType());
}
List<Expression> selectSymbols = SetQuery.getTypedProjectedSymbols(ResolverUtil.resolveElementsInGroup(inlineGroup, tma), actualTypes, tma);
Iterator<? extends Expression> iter = actualSymbols.iterator();
for (Expression ses : selectSymbols) {
ses = (Expression)ses.clone();
Expression actual = iter.next();
if (!Symbol.getShortName(ses).equals(Symbol.getShortName(actual))) {
if (ses instanceof AliasSymbol) {
((AliasSymbol)ses).setShortName(Symbol.getShortName(actual));
} else {
ses = new AliasSymbol(Symbol.getShortName(actual), ses);
}
}
select.addSymbol(ses);
}
query.setFrom(from);
QueryResolver.resolveCommand(query, tma);
query.setOption(nested.getOption()!=null?(Option) nested.getOption().clone():null);
from.getClauses().clear();
SubqueryFromClause sqfc = new SubqueryFromClause(inlineGroup.getName());
sqfc.setCommand(nested);
sqfc.getGroupSymbol().setMetadataID(inlineGroup.getMetadataID());
from.addClause(sqfc);
//copy the metadata onto the new query so that temp metadata adapters will be used in later calls
query.getTemporaryMetadata().getData().putAll(store.getData());
return query;
}
public static void makeSelectUnique(Select select, boolean expressionSymbolsOnly) {
select.setSymbols(select.getProjectedSymbols());
List<Expression> symbols = select.getSymbols();
HashSet<String> uniqueNames = new HashSet<String>();
for(int i = 0; i < symbols.size(); i++) {
Expression symbol = symbols.get(i);
String baseName = Symbol.getShortName(symbol);
String name = baseName;
int exprID = 0;
while (true) {
if (uniqueNames.add(name)) {
break;
}
name = baseName + '_' + (exprID++);
}
if (expressionSymbolsOnly && !(symbol instanceof ExpressionSymbol)) {
continue;
}
boolean hasAlias = false;
// Strip alias if it exists
if(symbol instanceof AliasSymbol) {
symbol = ((AliasSymbol)symbol).getSymbol();
hasAlias = true;
}
if (((symbol instanceof ExpressionSymbol) && !hasAlias) || !name.equalsIgnoreCase(baseName)) {
symbols.set(i, new AliasSymbol(name, symbol));
}
}
}
private Command rewriteUpdate(Update update) throws TeiidComponentException, TeiidProcessingException{
Command c = rewriteForWriteThrough(update);
if (c != null) {
return c;
}
UpdateInfo info = update.getUpdateInfo();
if (info != null && info.isInherentUpdate()) {
if (!info.getUnionBranches().isEmpty()) {
List<Command> batchedUpdates = new ArrayList<Command>(info.getUnionBranches().size() + 1);
for (UpdateInfo branchInfo : info.getUnionBranches()) {
batchedUpdates.add(rewriteInherentUpdate((Update)update.clone(), branchInfo));
}
batchedUpdates.add(0, rewriteInherentUpdate(update, info));
return new BatchedUpdateCommand(batchedUpdates, true);
}
return rewriteInherentUpdate(update, info);
}
boolean preserveUnknownOld = preserveUnknown;
preserveUnknown = true;
// Evaluate any function on the right side of set clauses
for (SetClause entry : update.getChangeList().getClauses()) {
entry.setValue(rewriteExpressionDirect(entry.getValue()));
}
preserveUnknown = preserveUnknownOld;
// Rewrite criteria
Criteria crit = update.getCriteria();
if(crit != null) {
preserveUnknown = false;
update.setCriteria(rewriteCriteria(crit));
preserveUnknown = preserveUnknownOld;
}
return update;
}
private Command rewriteInherentUpdate(Update update, UpdateInfo info)
throws QueryValidatorException, QueryMetadataException,
TeiidComponentException, QueryResolverException,
TeiidProcessingException {
UpdateMapping mapping = info.findUpdateMapping(update.getChangeList().getClauseMap().keySet(), false);
if (mapping == null) {
throw new QueryValidatorException(QueryPlugin.Event.TEIID30376, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30376, update.getChangeList().getClauseMap().keySet()));
}
Map<ElementSymbol, ElementSymbol> symbolMap = mapping.getUpdatableViewSymbols();
if (info.isSimple()) {
update.setGroup(mapping.getGroup().clone());
for (SetClause clause : update.getChangeList().getClauses()) {
clause.setSymbol(symbolMap.get(clause.getSymbol()));
}
//TODO: properly handle correlated references
DeepPostOrderNavigator.doVisit(update, new ExpressionMappingVisitor(symbolMap, true));
if (info.getViewDefinition().getCriteria() != null) {
update.setCriteria(Criteria.combineCriteria(update.getCriteria(), (Criteria)info.getViewDefinition().getCriteria().clone()));
}
//resolve
update.setUpdateInfo(ProcedureContainerResolver.getUpdateInfo(update.getGroup(), metadata, Command.TYPE_UPDATE, true));
return rewriteUpdate(update);
}
Query query = (Query)info.getViewDefinition().clone();
query.setOrderBy(null);
SymbolMap expressionMapping = SymbolMap.createSymbolMap(update.getGroup(), query.getProjectedSymbols(), metadata);
SetClauseList setClauseList = (SetClauseList) update.getChangeList().clone();
GroupSymbol varGroup = getVarGroup(update);
ArrayList<Expression> selectSymbols = mapChangeList(setClauseList, symbolMap, varGroup);
query.setSelect(new Select(selectSymbols));
ExpressionMappingVisitor emv = new ExpressionMappingVisitor(expressionMapping.asMap(), true);
PostOrderNavigator.doVisit(query.getSelect(), emv);
Criteria crit = update.getCriteria();
if (crit != null) {
PostOrderNavigator.doVisit(crit, emv);
query.setCriteria(Criteria.combineCriteria(query.getCriteria(), crit));
}
GroupSymbol group = mapping.getGroup();
String correlationName = mapping.getCorrelatedName().getName();
return createUpdateProcedure(update, query, group, correlationName, setClauseList, varGroup, null);
}
private ArrayList<Expression> mapChangeList(SetClauseList setClauses,
Map<ElementSymbol, ElementSymbol> symbolMap, GroupSymbol varGroup) {
ArrayList<Expression> selectSymbols = new ArrayList<Expression>(setClauses.getClauses().size());
int i = 0;
for (SetClause clause : setClauses.getClauses()) {
Expression ex = clause.getValue();
if (!EvaluatableVisitor.willBecomeConstant(ex)) {
ex = mapExpression(varGroup, selectSymbols, i, ex);
clause.setValue(ex);
}
if (symbolMap != null) {
clause.setSymbol(symbolMap.get(clause.getSymbol()));
}
i++;
}
return selectSymbols;
}
private static Expression mapExpression(GroupSymbol varGroup,
ArrayList<Expression> selectSymbols, int i, Expression ex) {
String name = "s_" +i; //$NON-NLS-1$
selectSymbols.add(new AliasSymbol(name, ex));
ex = new ElementSymbol(name, varGroup.clone());
return ex;
}
private Command createUpdateProcedure(Update update, Query query,
GroupSymbol group, String correlationName, SetClauseList setClauseList, GroupSymbol varGroup, Criteria constraint)
throws TeiidComponentException, QueryMetadataException,
QueryResolverException, TeiidProcessingException {
Update newUpdate = new Update();
newUpdate.setConstraint(constraint);
newUpdate.setChangeList(setClauseList);
newUpdate.setGroup(group.clone());
List<Criteria> pkCriteria = createPkCriteria(group, correlationName, query, varGroup);
newUpdate.setCriteria(new CompoundCriteria(pkCriteria));
return asLoopProcedure(update.getGroup(), query, newUpdate, varGroup, Command.TYPE_UPDATE);
}
/**
* rewrite as loop on (query) as X begin newupdate; rows_updated = rows_updated + 1 end;
* @param updateType
*/
private Command asLoopProcedure(GroupSymbol group, QueryCommand query,
ProcedureContainer newUpdate, GroupSymbol varGroup, int updateType) throws QueryResolverException,
TeiidComponentException, TeiidProcessingException {
return asLoopProcedure(group, query, new CommandStatement(newUpdate), varGroup, updateType);
}
private Command asLoopProcedure(GroupSymbol group, QueryCommand query,
Statement s, GroupSymbol varGroup, int updateType) throws QueryResolverException,
TeiidComponentException, TeiidProcessingException {
Block b = new Block();
b.addStatement(s);
CreateProcedureCommand cupc = new CreateProcedureCommand();
cupc.setUpdateType(updateType);
Block parent = new Block();
parent.setAtomic(true);
ElementSymbol rowsUpdated = new ElementSymbol(ProcedureReservedWords.VARIABLES+Symbol.SEPARATOR+"ROWS_UPDATED"); //$NON-NLS-1$
DeclareStatement ds = new DeclareStatement(rowsUpdated, DataTypeManager.DefaultDataTypes.INTEGER, new Constant(0));
parent.addStatement(ds);
LoopStatement ls = new LoopStatement(b, query, varGroup.getName());
parent.addStatement(ls);
AssignmentStatement as = new AssignmentStatement();
rowsUpdated.setType(DataTypeManager.DefaultDataClasses.INTEGER);
as.setVariable(rowsUpdated);
as.setExpression(new Function("+", new Expression[] {rowsUpdated, new Constant(1)})); //$NON-NLS-1$
b.addStatement(as);
Query returnQuery = new Query();
returnQuery.setSelect(new Select(Arrays.asList(rowsUpdated.clone())));
parent.addStatement(new CommandStatement(returnQuery));
cupc.setBlock(parent);
cupc.setVirtualGroup(group);
QueryResolver.resolveCommand(cupc, metadata);
return rewrite(cupc, metadata, context);
}
private List<Criteria> createPkCriteria(GroupSymbol group, String correlationName, Query query, GroupSymbol varGroup) throws TeiidComponentException, QueryMetadataException {
Object pk = metadata.getPrimaryKey(group.getMetadataID());
if (pk == null) {
pk = metadata.getUniqueKeysInGroup(group.getMetadataID()).iterator().next();
}
int i = query.getSelect().getSymbols().size();
List<Object> ids = metadata.getElementIDsInKey(pk);
List<Criteria> pkCriteria = new ArrayList<Criteria>(ids.size());
for (Object object : ids) {
ElementSymbol es = new ElementSymbol(correlationName + Symbol.SEPARATOR + metadata.getName(object));
query.getSelect().addSymbol(new AliasSymbol("s_" +i, es)); //$NON-NLS-1$
es = new ElementSymbol(group.getName() + Symbol.SEPARATOR + metadata.getName(object));
pkCriteria.add(new CompareCriteria(es, CompareCriteria.EQ, new ElementSymbol("s_" + i, varGroup.clone()))); //$NON-NLS-1$
i++;
}
return pkCriteria;
}
private Command rewriteDelete(Delete delete) throws TeiidComponentException, TeiidProcessingException{
Command c = rewriteForWriteThrough(delete);
if (c != null) {
return c;
}
UpdateInfo info = delete.getUpdateInfo();
if (info != null && info.isInherentDelete()) {
if (!info.getUnionBranches().isEmpty()) {
List<Command> batchedUpdates = new ArrayList<Command>(info.getUnionBranches().size() + 1);
for (UpdateInfo branchInfo : info.getUnionBranches()) {
batchedUpdates.add(rewriteInherentDelete((Delete)delete.clone(), branchInfo));
}
batchedUpdates.add(0, rewriteInherentDelete(delete, info));
return new BatchedUpdateCommand(batchedUpdates, true);
}
return rewriteInherentDelete(delete, info);
}
// Rewrite criteria
Criteria crit = delete.getCriteria();
if(crit != null) {
boolean preserveUnknownOld = preserveUnknown;
preserveUnknown = false;
delete.setCriteria(rewriteCriteria(crit));
preserveUnknown = preserveUnknownOld;
}
return delete;
}
private Command rewriteInherentDelete(Delete delete, UpdateInfo info)
throws QueryMetadataException, TeiidComponentException,
QueryResolverException, TeiidProcessingException {
UpdateMapping mapping = info.getDeleteTarget();
if (info.isSimple()) {
delete.setGroup(mapping.getGroup().clone());
//TODO: properly handle correlated references
DeepPostOrderNavigator.doVisit(delete, new ExpressionMappingVisitor(mapping.getUpdatableViewSymbols(), true));
delete.setUpdateInfo(ProcedureContainerResolver.getUpdateInfo(delete.getGroup(), metadata, Command.TYPE_DELETE, true));
if (info.getViewDefinition().getCriteria() != null) {
delete.setCriteria(Criteria.combineCriteria(delete.getCriteria(), (Criteria)info.getViewDefinition().getCriteria().clone()));
}
return rewriteDelete(delete);
}
Query query = (Query)info.getViewDefinition().clone();
query.setOrderBy(null);
SymbolMap expressionMapping = SymbolMap.createSymbolMap(delete.getGroup(), query.getProjectedSymbols(), metadata);
query.setSelect(new Select());
ExpressionMappingVisitor emv = new ExpressionMappingVisitor(expressionMapping.asMap(), true);
Criteria crit = delete.getCriteria();
if (crit != null) {
PostOrderNavigator.doVisit(crit, emv);
query.setCriteria(Criteria.combineCriteria(query.getCriteria(), crit));
}
GroupSymbol group = mapping.getGroup();
String correlationName = mapping.getCorrelatedName().getName();
return createDeleteProcedure(delete, query, group, correlationName);
}
public static Command createDeleteProcedure(Delete delete, QueryMetadataInterface metadata, CommandContext context) throws QueryResolverException, QueryMetadataException, TeiidComponentException, TeiidProcessingException {
QueryRewriter rewriter = new QueryRewriter(metadata, context);
Criteria crit = delete.getCriteria();
Query query = new Query(new Select(), new From(Arrays.asList(new UnaryFromClause(delete.getGroup()))), crit, null, null);
return rewriter.createDeleteProcedure(delete, query, delete.getGroup(), delete.getGroup().getName());
}
public static Command createUpdateProcedure(Update update, QueryMetadataInterface metadata, CommandContext context) throws QueryResolverException, QueryMetadataException, TeiidComponentException, TeiidProcessingException {
QueryRewriter rewriter = new QueryRewriter(metadata, context);
Criteria crit = update.getCriteria();
if (crit != null) {
crit = (Criteria) crit.clone();
}
SetClauseList setClauseList = (SetClauseList) update.getChangeList().clone();
GroupSymbol varGroup = getVarGroup(update);
ArrayList<Expression> selectSymbols = rewriter.mapChangeList(setClauseList, null, varGroup);
Criteria constraint = null;
if (update.getConstraint() != null) {
constraint = update.getConstraint();
Map<ElementSymbol, Expression> map = null;
Collection<ElementSymbol> elems = ElementCollectorVisitor.getElements(update.getConstraint(), true);
Set<ElementSymbol> existing = setClauseList.getClauseMap().keySet();
for (ElementSymbol es : elems) {
if (existing.contains(es)) {
continue;
}
if (map == null) {
map = new HashMap<ElementSymbol, Expression>();
}
map.put(es, mapExpression(varGroup, selectSymbols, selectSymbols.size(), es));
}
if (map != null) {
constraint = (Criteria)constraint.clone();
ExpressionMappingVisitor.mapExpressions(constraint, map);
}
}
Query query = new Query(new Select(selectSymbols), new From(Arrays.asList(new UnaryFromClause(update.getGroup()))), crit, null, null);
return rewriter.createUpdateProcedure(update, query, update.getGroup(), update.getGroup().getName(), setClauseList, varGroup, constraint);
}
private Command createDeleteProcedure(Delete delete, Query query,
GroupSymbol group, String correlationName)
throws TeiidComponentException, QueryMetadataException,
QueryResolverException, TeiidProcessingException {
Delete newUpdate = new Delete();
newUpdate.setGroup(group.clone());
GroupSymbol varGroup = getVarGroup(delete);
List<Criteria> pkCriteria = createPkCriteria(group, correlationName, query, varGroup);
newUpdate.setCriteria(new CompoundCriteria(pkCriteria));
return asLoopProcedure(delete.getGroup(), query, newUpdate, varGroup, Command.TYPE_DELETE);
}
private Limit rewriteLimitClause(Limit limit) throws TeiidComponentException, TeiidProcessingException{
if (limit.getOffset() != null) {
if (!processing) {
limit.setOffset(rewriteExpressionDirect(limit.getOffset()));
} else {
Constant c = evaluate(limit.getOffset(), false);
limit.setOffset(c);
ValidationVisitor.LIMIT_CONSTRAINT.validate(c.getValue());
}
if (ZERO_CONSTANT.equals(limit.getOffset())) {
limit.setOffset(null);
}
}
if (limit.getRowLimit() != null) {
if (!processing) {
limit.setRowLimit(rewriteExpressionDirect(limit.getRowLimit()));
} else {
Constant c = evaluate(limit.getRowLimit(), false);
limit.setRowLimit(c);
ValidationVisitor.LIMIT_CONSTRAINT.validate(c.getValue());
}
}
return limit;
}
}