blob: 4777dc1b9a3b7576180fec5e4dc159ed2b30d882 [file] [log] [blame]
/*=========================================================================
* Copyright Copyright (c) 2000-2014 Pivotal Software, Inc. All Rights Reserved.
* This product is protected by U.S. and international copyright
* and intellectual property laws. Pivotal products are covered by
* more patents listed at http://www.pivotal.io/patents.
* $Id: Compiler.java,v 1.1 2005/01/27 06:26:33 vaibhav Exp $
*=========================================================================
*/
package com.gemstone.gemfire.cache.query.internal;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.apache.logging.log4j.Logger;
import antlr.collections.AST;
import antlr.debug.misc.ASTFrame;
import com.gemstone.gemfire.cache.query.FunctionDomainException;
import com.gemstone.gemfire.cache.query.NameResolutionException;
import com.gemstone.gemfire.cache.query.QueryException;
import com.gemstone.gemfire.cache.query.QueryInvalidException;
import com.gemstone.gemfire.cache.query.QueryInvocationTargetException;
import com.gemstone.gemfire.cache.query.TypeMismatchException;
import com.gemstone.gemfire.cache.query.internal.parse.GemFireAST;
import com.gemstone.gemfire.cache.query.internal.parse.OQLLexer;
import com.gemstone.gemfire.cache.query.internal.parse.OQLLexerTokenTypes;
import com.gemstone.gemfire.cache.query.internal.parse.OQLParser;
import com.gemstone.gemfire.cache.query.internal.types.CollectionTypeImpl;
import com.gemstone.gemfire.cache.query.internal.types.MapTypeImpl;
import com.gemstone.gemfire.cache.query.internal.types.ObjectTypeImpl;
import com.gemstone.gemfire.cache.query.internal.types.TypeUtils;
import com.gemstone.gemfire.cache.query.types.CollectionType;
import com.gemstone.gemfire.cache.query.types.MapType;
import com.gemstone.gemfire.cache.query.types.ObjectType;
import com.gemstone.gemfire.i18n.LogWriterI18n;
import com.gemstone.gemfire.internal.Assert;
import com.gemstone.gemfire.internal.InternalDataSerializer;
import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
import com.gemstone.gemfire.internal.logging.LogService;
/**
* Class Description
*
* @version $Revision: 1.1 $s
* @author ericz
* @author asif
*/
public class QCompiler implements OQLLexerTokenTypes {
private static final Logger logger = LogService.getLogger();
private Stack stack = new Stack ();
private Map imports = new HashMap ();
final private boolean isForIndexCompilation;
private boolean traceOn;
public QCompiler() {
this.isForIndexCompilation = false;
}
public QCompiler(boolean isForIndexCompilation) {
this.isForIndexCompilation = isForIndexCompilation;
}
/* compile the string into a Query (returns the root CompiledValue)
*/
public CompiledValue compileQuery(String oqlSource) {
try {
OQLLexer lexer = new OQLLexer (new StringReader (oqlSource));
OQLParser parser = new OQLParser (lexer);
// by default use Unsupported AST class, overridden for supported
// operators in the grammer proper
parser.setASTNodeClass ("com.gemstone.gemfire.cache.query.internal.parse.ASTUnsupported");
parser.queryProgram ();
GemFireAST n = (GemFireAST)parser.getAST ();
n.compile(this);
} catch (Exception ex){ // This is to make sure that we are wrapping any antlr exception with GemFire Exception.
throw new QueryInvalidException(LocalizedStrings.QCompiler_SYNTAX_ERROR_IN_QUERY_0.toLocalizedString(ex.getMessage()), ex);
}
Assert.assertTrue (stackSize () == 1, "stack size = " + stackSize ());
return (CompiledValue)pop ();
}
/** Returns List<CompiledIteratorDef> */
public List compileFromClause(String fromClause) {
try {
OQLLexer lexer = new OQLLexer (new StringReader (fromClause));
OQLParser parser = new OQLParser (lexer);
// by default use Unsupported AST class, overridden for supported
// operators in the grammer proper
parser.setASTNodeClass ("com.gemstone.gemfire.cache.query.internal.parse.ASTUnsupported");
parser.loneFromClause ();
GemFireAST n = (GemFireAST)parser.getAST ();
n.compile(this);
} catch (Exception ex){ // This is to make sure that we are wrapping any antlr exception with GemFire Exception.
throw new QueryInvalidException(LocalizedStrings.QCompiler_SYNTAX_ERROR_IN_QUERY_0.toLocalizedString(ex.getMessage()), ex);
}
Assert.assertTrue (stackSize () == 1, "stack size = " + stackSize ());
return (List)pop ();
}
/** Returns List<CompiledIteratorDef> or null if projectionAttrs is '*' */
public List compileProjectionAttributes(String projectionAttributes) {
try {
OQLLexer lexer = new OQLLexer (new StringReader (projectionAttributes));
OQLParser parser = new OQLParser (lexer);
// by default use Unsupported AST class, overridden for supported
// operators in the grammer proper
parser.setASTNodeClass ("com.gemstone.gemfire.cache.query.internal.parse.ASTUnsupported");
parser.loneProjectionAttributes ();
GemFireAST n = (GemFireAST)parser.getAST ();
// don't compile TOK_STAR
if (n.getType () == TOK_STAR) {
return null;
}
n.compile(this);
} catch (Exception ex){ // This is to make sure that we are wrapping any antlr exception with GemFire Exception.
throw new QueryInvalidException(LocalizedStrings.QCompiler_SYNTAX_ERROR_IN_QUERY_0.toLocalizedString(ex.getMessage()), ex);
}
Assert.assertTrue(stackSize () == 1,
"stack size = " + stackSize () +
";stack=" + this.stack);
return (List)pop ();
}
/**
* Yogesh: compiles order by clause and push into the stack
* @param numOfChildren
*/
public void compileOrederByClause(int numOfChildren) {
List list = new ArrayList();
for (int i = 0; i < numOfChildren; i++) {
CompiledSortCriterion csc = (CompiledSortCriterion)this.stack.pop();
list.add(0, csc);
}
push(list) ;
}
public void compileGroupByClause(int numOfChildren) {
List list = new ArrayList();
for (int i = 0; i < numOfChildren; i++) {
Object csc = this.stack.pop();
list.add(0, csc);
}
push(list);
}
/**
* Yogesh: compiles sort criteria present in order by clause and push into the stack
* @param sortCriterion
*/
public void compileSortCriteria(String sortCriterion) {
CompiledValue obj = (CompiledValue)this.stack.pop();
boolean criterion = false;
if (sortCriterion.equals("desc"))
criterion = true;
CompiledSortCriterion csc = new CompiledSortCriterion(criterion, obj);
push(csc);
}
public void compileLimit(String limitNum) {
push(Integer.valueOf(limitNum));
}
/** Processes import statements only. This compiler instance remembers the imports
* and can be used to compile other strings with this context info
*/
public void compileImports(String imports) {
try {
OQLLexer lexer = new OQLLexer (new StringReader (imports));
OQLParser parser = new OQLParser (lexer);
// by default use Unsupported AST class, overridden for supported
// operators in the grammer proper
parser.setASTNodeClass ("com.gemstone.gemfire.cache.query.internal.parse.ASTUnsupported");
parser.loneImports();
GemFireAST n = (GemFireAST)parser.getAST ();
n.compile(this);
} catch (Exception ex){ // This is to make sure that we are wrapping any antlr exception with GemFire Exception.
throw new QueryInvalidException(LocalizedStrings.QCompiler_SYNTAX_ERROR_IN_QUERY_0.toLocalizedString(ex.getMessage()), ex);
}
Assert.assertTrue(stackSize() == 0,
"stack size = " + stackSize() +
";stack=" + this.stack);
}
public void select(Map<Integer, Object> queryComponents) {
CompiledValue limit = null;
Object limitObject = queryComponents.remove(OQLLexerTokenTypes.LIMIT);
if (limitObject instanceof Integer) {
limit = new CompiledLiteral(limitObject);
} else {
limit = (CompiledBindArgument) limitObject;
}
List<CompiledSortCriterion> orderByAttrs = (List<CompiledSortCriterion>) queryComponents
.remove(OQLLexerTokenTypes.LITERAL_order);
List iterators = (List) queryComponents
.remove(OQLLexerTokenTypes.LITERAL_from);
List projAttrs = (List) queryComponents
.remove(OQLLexerTokenTypes.PROJECTION_ATTRS);
if (projAttrs == null) {
// remove any * or all attribute
queryComponents.remove(OQLLexerTokenTypes.TOK_STAR);
queryComponents.remove(OQLLexerTokenTypes.LITERAL_all);
}
// "COUNT" or null
/*String aggrExpr = (String) queryComponents
.remove(OQLLexerTokenTypes.LITERAL_count);*/
// "DISTINCT" or null
String distinct = (String) queryComponents
.remove(OQLLexerTokenTypes.LITERAL_distinct);
List<String> hints = null;
Object hintObject = queryComponents.remove(OQLLexerTokenTypes.LITERAL_hint);
if (hintObject != null) {
hints = (List<String>) hintObject;
}
List<CompiledValue> groupByClause = (List<CompiledValue>) queryComponents
.remove(OQLLexerTokenTypes.LITERAL_group);
// whatever remains , treat it as where
// whereClause
CompiledValue where = null;
if (queryComponents.size() == 1) {
where = (CompiledValue) queryComponents.values().iterator().next();
} else if (queryComponents.size() > 1) {
throw new QueryInvalidException(
"Unexpected/unsupported query clauses found");
}
LinkedHashMap<Integer, CompiledAggregateFunction> aggMap = identifyAggregateExpressions(projAttrs);
boolean isCountOnly = checkForCountOnly(aggMap, projAttrs, groupByClause);
if(isCountOnly) {
projAttrs = null;
}
CompiledSelect select = createSelect(distinct != null,
isCountOnly, where, iterators, projAttrs, orderByAttrs, limit,
hints, groupByClause, aggMap);
push(select);
}
private boolean checkForCountOnly(
Map<Integer, CompiledAggregateFunction> aggregateMap, List projAttribs, List<CompiledValue> groupBy) {
if (aggregateMap != null && aggregateMap.size() == 1
&& projAttribs.size() == 1 && groupBy == null) {
for (Map.Entry<Integer, CompiledAggregateFunction> entry : aggregateMap
.entrySet()) {
CompiledAggregateFunction caf = entry.getValue();
if (caf.getFunctionType() == OQLLexerTokenTypes.COUNT
&& caf.getParameter() == null) {
return true;
}
}
}
return false;
}
private CompiledSelect createSelect(boolean isDistinct, boolean isCountOnly, CompiledValue where,
List iterators, List projAttrs, List<CompiledSortCriterion> orderByAttrs, CompiledValue limit,
List<String> hints,List<CompiledValue> groupByClause, LinkedHashMap<Integer,
CompiledAggregateFunction> aggMap ) {
if(isCountOnly || (groupByClause == null && aggMap == null)
|| (aggMap == null && orderByAttrs == null)) {
return new CompiledSelect(isDistinct,
isCountOnly, where, iterators, projAttrs, orderByAttrs, limit,
hints, groupByClause);
}else {
return new CompiledGroupBySelect(isDistinct,
isCountOnly, where, iterators, projAttrs, orderByAttrs, limit,
hints, groupByClause, aggMap);
}
}
private LinkedHashMap<Integer, CompiledAggregateFunction> identifyAggregateExpressions(List projAttribs) {
if(projAttribs != null) {
LinkedHashMap<Integer, CompiledAggregateFunction> mapping = new LinkedHashMap<Integer,CompiledAggregateFunction>();
int index = 0;
for(Object o : projAttribs) {
CompiledValue proj =(CompiledValue) ((Object[])o)[1];
if( proj.getType() == OQLLexerTokenTypes.AGG_FUNC) {
mapping.put(index, (CompiledAggregateFunction)proj);
}
++index;
}
return mapping.size() == 0 ? null : mapping;
}else {
return null;
}
}
public void projection () {
// find an id or null on the stack, then an expr CompiledValue
// push an Object[2] on the stack. First element is id, second is CompiledValue
CompiledID id = (CompiledID)pop ();
CompiledValue expr = (CompiledValue)pop ();
push (new Object[] {id == null ? null : id.getId (), expr});
}
public void aggregateFunction (CompiledValue expr, int aggFuncType, boolean distinctOnly) {
push (new CompiledAggregateFunction(expr, aggFuncType, distinctOnly));
}
public void iteratorDef () {
// find type id and colln on the stack
ObjectType type = assembleType(); // can be null
CompiledID id = (CompiledID)TypeUtils.checkCast(pop(), CompiledID.class); // can be null
CompiledValue colln = (CompiledValue)TypeUtils.checkCast(pop(), CompiledValue.class);
if (type == null) {
type = TypeUtils.OBJECT_TYPE;
}
push (new CompiledIteratorDef (id == null ? null : id.getId (),
type, colln));
}
public void undefinedExpr (boolean is_defined) {
CompiledValue value = (CompiledValue)pop ();
push (new CompiledUndefined (value, is_defined));
}
public void function (int function, int numOfChildren) {
CompiledValue [] cvArr = new CompiledValue[numOfChildren];
for(int i = numOfChildren - 1; i >= 0; i-- ) {
cvArr[i] = (CompiledValue) pop();
}
push(new CompiledFunction(cvArr, function));
}
public void inExpr () {
CompiledValue collnExpr = (CompiledValue)TypeUtils.checkCast (pop (), CompiledValue.class);
CompiledValue elm = (CompiledValue)TypeUtils.checkCast (pop (), CompiledValue.class);
push (new CompiledIn (elm, collnExpr));
}
public void constructObject (Class clazz) {
// find argList on stack
// only support SET for now
Assert.assertTrue (clazz == ResultsSet.class);
List argList = (List)TypeUtils.checkCast (pop (), List.class);
push (new CompiledConstruction (clazz, argList));
}
public void pushId (String id) {
push (new CompiledID (id));
}
public void pushRegion (String regionPath) {
push (new CompiledRegion (regionPath));
}
public void appendPathComponent (String id) {
CompiledValue rcvr = (CompiledValue)pop ();
push (new CompiledPath (rcvr, id));
}
public void pushBindArgument (int i) {
push (new CompiledBindArgument (i));
}
public void pushLiteral (Object obj) {
push (new CompiledLiteral (obj));
}
public void pushNull () // used as a placeholder for a missing clause
{
push (null);
}
public void combine (int num) {
List list = new ArrayList ();
for (int i =0; i < num; i++) {
list.add (0,pop ());
}
push (list);
}
public void methodInvocation () {
// find on stack:
// argList, methodName, receiver (which may be null if receiver is implicit)
List argList = (List)TypeUtils.checkCast (pop (), List.class);
CompiledID methodName = (CompiledID)TypeUtils.checkCast (pop (), CompiledID.class);
CompiledValue rcvr = (CompiledValue)TypeUtils.checkCast (pop (), CompiledValue.class);
push (new CompiledOperation (rcvr, methodName.getId (), argList));
}
public void indexOp()
{
// find the List of index expressions and the receiver on the stack
Object indexParams = pop();
final CompiledValue rcvr = (CompiledValue)TypeUtils.checkCast(pop(),
CompiledValue.class);
CompiledValue indexExpr = CompiledValue.MAP_INDEX_ALL_KEYS;
if (indexParams != null) {
final List indexList = (List)TypeUtils.checkCast(indexParams, List.class);
if (!isForIndexCompilation && indexList.size() != 1) {
throw new UnsupportedOperationException(
LocalizedStrings.QCompiler_ONLY_ONE_INDEX_EXPRESSION_SUPPORTED
.toLocalizedString());
}
if (indexList.size() == 1) {
indexExpr = (CompiledValue)TypeUtils.checkCast(indexList.get(0),
CompiledValue.class);
if (indexExpr.getType() == TOK_COLON) {
throw new UnsupportedOperationException(
LocalizedStrings.QCompiler_RANGES_NOT_SUPPORTED_IN_INDEX_OPERATORS
.toLocalizedString());
}
indexExpr = (CompiledValue)TypeUtils.checkCast(indexList.get(0),
CompiledValue.class);
push(new CompiledIndexOperation(rcvr, indexExpr));
}
else {
assert this.isForIndexCompilation;
MapIndexable mi = new MapIndexOperation(rcvr, indexList);
push(mi);
}
}else {
if(!this.isForIndexCompilation) {
throw new QueryInvalidException(
LocalizedStrings.QCompiler_SYNTAX_ERROR_IN_QUERY_0.toLocalizedString("* use incorrect"));
}
push(new CompiledIndexOperation(rcvr, indexExpr));
}
}
/**
* Creates appropriate CompiledValue for the like predicate based on the
* sargability of the String or otherwise. It also works on the last character
* to see if the sargable like predicate results in a CompiledJunction or a
* Comparison. Currently we are supporting only the '%' terminated "like"
* predicate.
*
* @param var
* The CompiledValue representing the variable
* @param patternOrBindParam
* The CompiledLiteral reprsenting the pattern of the like
* predicate
* @return CompiledValue representing the "like" predicate
*
*/
CompiledValue createCompiledValueForLikePredicate(CompiledValue var,
CompiledValue patternOrBindParam) {
if(!(patternOrBindParam.getType() == CompiledBindArgument.QUERY_PARAM)) {
CompiledLiteral pattern = (CompiledLiteral)patternOrBindParam;
if (pattern._obj == null) {
throw new UnsupportedOperationException("Null values are not supported with LIKE predicate.");
}
}
// From 6.6 Like is enhanced to support special character (% and _) at any
// position of the string.
return new CompiledLike(var,patternOrBindParam);
}
public void like() {
CompiledValue v2 = (CompiledValue)pop();
CompiledValue v1 = (CompiledValue)pop();
CompiledValue cv = createCompiledValueForLikePredicate(v1, v2);
push(cv);
}
public void compare (int opKind) {
CompiledValue v2 = (CompiledValue)pop ();
CompiledValue v1 = (CompiledValue)pop ();
push (new CompiledComparison (v1, v2, opKind));
}
public void or (int numTerms) {
junction (numTerms, LITERAL_or);
}
public void and (int numTerms) {
junction (numTerms, LITERAL_and);
}
private void junction (int numTerms, int operator) {
/* if any of the operands are junctions with same operator as this one
* then flatten */
List operands = new ArrayList (numTerms);
for (int i = 0; i < numTerms; i++) {
CompiledValue operand = (CompiledValue)pop ();
// flatten if we can
if (operand instanceof CompiledJunction &&
((CompiledJunction)operand).getOperator () == operator) {
CompiledJunction junction = (CompiledJunction)operand;
List jOperands = junction.getOperands ();
for (int j = 0; j < jOperands.size (); j++)
operands.add (jOperands.get (j));
} else
operands.add (operand);
}
push (new CompiledJunction (
(CompiledValue[])operands.toArray (new CompiledValue[operands.size ()]),
operator));
}
public void not () {
Object obj = this.stack.peek ();
Assert.assertTrue (obj instanceof CompiledValue);
if (obj instanceof Negatable)
((Negatable)obj).negate ();
else
push(new CompiledNegation ((CompiledValue)pop ()));
}
public void unaryMinus() {
Object obj = this.stack.peek ();
Assert.assertTrue (obj instanceof CompiledValue);
push(new CompiledUnaryMinus ((CompiledValue)pop ()));
}
public void typecast() {
// pop expr and type, apply type, then push result
AbstractCompiledValue cmpVal = (AbstractCompiledValue)
TypeUtils.checkCast(pop(), AbstractCompiledValue.class);
ObjectType objType = assembleType();
cmpVal.setTypecast(objType);
push(cmpVal);
}
// returns null if null is on the stack
public ObjectType assembleType() {
ObjectType objType = (ObjectType)TypeUtils.checkCast(pop(), ObjectType.class);
if (objType instanceof CollectionType) {
// pop the elementType
ObjectType elementType = assembleType();
if (objType instanceof MapType) {
//pop the key type
ObjectType keyType = assembleType();
return new MapTypeImpl(objType.resolveClass(), keyType, elementType);
}
return new CollectionTypeImpl(objType.resolveClass(), elementType);
}
return objType;
}
public void traceRequest() {
this.traceOn = true;
}
public boolean isTraceRequested() {
return traceOn;
}
public void setHint(int numOfChildren) {
ArrayList list = new ArrayList();
for (int i = 0; i < numOfChildren; i++) {
String hi = (String)this.stack.pop();
list.add(0, hi);
}
push(list);
//setHints(list);
}
public void setHintIdentifier(String text) {
push(text);
}
public void importName (String qualifiedName, String asName) {
if (asName == null) {
// if no AS, then use the short name from qualifiedName
// as the AS
int idx = qualifiedName.lastIndexOf ('.');
if (idx >= 0) {
asName = qualifiedName.substring (idx + 1);
} else {
asName = qualifiedName;
}
}
if (logger.isTraceEnabled()) {
logger.trace("QCompiler.importName: {},{}", asName, qualifiedName);
}
this.imports.put(asName, qualifiedName);
}
public Object pop () {
Object obj = this.stack.pop ();
if (logger.isTraceEnabled()) {
logger.trace("QCompiler.pop: {}", obj);
}
return obj;
}
public void push (Object obj) {
if (logger.isTraceEnabled()) {
logger.trace("QCompiler.push: {}", obj);
}
this.stack.push (obj);
}
public int stackSize () {
return this.stack.size ();
}
public ObjectType resolveType (String typeName) {
if (typeName == null) {
if (logger.isTraceEnabled()) {
logger.trace("QCompiler.resolveType= {}", Object.class.getName());
}
return TypeUtils.OBJECT_TYPE;
}
// resolve with imports
String as = null;
if (this.imports != null) {
as = (String)this.imports.get (typeName);
}
if (as != null) typeName = as;
Class resultClass;
try {
resultClass = InternalDataSerializer.getCachedClass(typeName);
} catch (ClassNotFoundException e) {
throw new QueryInvalidException(LocalizedStrings.QCompiler_TYPE_NOT_FOUND_0.toLocalizedString(typeName), e);
}
if (logger.isTraceEnabled()) {
logger.trace("QCompiler.resolveType= {}", resultClass.getName());
}
return new ObjectTypeImpl(resultClass);
}
private static class MapIndexOperation extends AbstractCompiledValue implements MapIndexable {
private final CompiledValue rcvr;
private final List<CompiledValue> indexList;
public MapIndexOperation (CompiledValue rcvr, List<CompiledValue> indexList ) {
this.rcvr = rcvr;
this.indexList = indexList;
}
public CompiledValue getRecieverSansIndexArgs()
{
return rcvr;
}
public CompiledValue getMapLookupKey()
{
throw new UnsupportedOperationException("Function invocation not expected");
}
public List<CompiledValue> getIndexingKeys()
{
return (List<CompiledValue>)indexList;
}
public Object evaluate(ExecutionContext context)
throws FunctionDomainException, TypeMismatchException,
NameResolutionException, QueryInvocationTargetException
{
throw new UnsupportedOperationException("Method execution not expected");
}
public int getType()
{
throw new UnsupportedOperationException("Method execution not expected");
}
}
}