package com.mysema.rdfbean.rdb;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import javax.annotation.Nullable;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.mysema.query.BooleanBuilder;
import com.mysema.query.QueryMetadata;
import com.mysema.query.sql.SQLCommonQuery;
import com.mysema.query.sql.SQLQuery;
import com.mysema.query.sql.SQLSubQuery;
import com.mysema.query.types.Constant;
import com.mysema.query.types.ConstantImpl;
import com.mysema.query.types.Expression;
import com.mysema.query.types.FactoryExpression;
import com.mysema.query.types.Operation;
import com.mysema.query.types.OperationImpl;
import com.mysema.query.types.Operator;
import com.mysema.query.types.Ops;
import com.mysema.query.types.OrderSpecifier;
import com.mysema.query.types.ParamExpression;
import com.mysema.query.types.Path;
import com.mysema.query.types.Predicate;
import com.mysema.query.types.PredicateOperation;
import com.mysema.query.types.SubQueryExpression;
import com.mysema.query.types.TemplateExpression;
import com.mysema.query.types.TemplateExpressionImpl;
import com.mysema.query.types.path.NumberPath;
import com.mysema.query.types.template.BooleanTemplate;
import com.mysema.rdfbean.model.Block;
import com.mysema.rdfbean.model.ContainerBlock;
import com.mysema.rdfbean.model.GraphBlock;
import com.mysema.rdfbean.model.GroupBlock;
import com.mysema.rdfbean.model.ID;
import com.mysema.rdfbean.model.LIT;
import com.mysema.rdfbean.model.NODE;
import com.mysema.rdfbean.model.OptionalBlock;
import com.mysema.rdfbean.model.PatternBlock;
import com.mysema.rdfbean.model.QueryLanguage;
import com.mysema.rdfbean.model.RDFVisitor;
import com.mysema.rdfbean.model.UID;
import com.mysema.rdfbean.model.UnionBlock;
import com.mysema.rdfbean.query.VarNameIterator;
import com.mysema.rdfbean.xsd.ConverterRegistryImpl;
/**
* @author tiwe
*
*/
public class RDBRDFVisitor implements RDFVisitor<Object, QueryMetadata> {
@SuppressWarnings("unchecked")
private static final Set<Operator<?>> MATH = new HashSet<Operator<?>>(Arrays.asList(
Ops.LT, Ops.GT, Ops.LOE, Ops.GOE,
Ops.MathOps.ABS, Ops.ADD, Ops.SUB, Ops.MULT, Ops.MOD, Ops.DIV, Ops.AggOps.AVG_AGG, Ops.AggOps.SUM_AGG));
@SuppressWarnings("unchecked")
private static final Set<Operator<?>> DATE = new HashSet<Operator<?>>(Arrays.asList(
Ops.DateTimeOps.DAY_OF_MONTH, Ops.DateTimeOps.DAY_OF_WEEK, Ops.DateTimeOps.DAY_OF_YEAR, Ops.DateTimeOps.HOUR,
Ops.DateTimeOps.MILLISECOND, Ops.DateTimeOps.MINUTE, Ops.DateTimeOps.MONTH, Ops.DateTimeOps.SECOND, Ops.DateTimeOps.WEEK,
Ops.DateTimeOps.YEAR, Ops.DateTimeOps.YEAR_MONTH));
private final RDBContext context;
private final Set<Expression<?>> namedExpressions = new HashSet<Expression<?>>();
private Map<Expression<?>, Expression<?>> exprToSymbol = new HashMap<Expression<?>, Expression<?>>();
private Map<Expression<?>, Path<Long>> exprToMapped = new HashMap<Expression<?>, Path<Long>>();
private final Set<Expression<?>> resources = new HashSet<Expression<?>>();
private final Set<QSymbol> numericSymbols = new HashSet<QSymbol>();
private final Set<QSymbol> dateTimeSymbols = new HashSet<QSymbol>();
private final Stack<Expression<UID>> graphs = new Stack<Expression<UID>>();
private final Stack<Operator<?>> operators = new Stack<Operator<?>>();
private final Function<Long, NODE> transformer;
private final VarNameIterator stmts = new VarNameIterator("stmts");
private final VarNameIterator symbols = new VarNameIterator("symbols");
private SQLCommonQuery<?> query;
private boolean inOptional;
private boolean firstSource = true;
private boolean asLiteral = false;
public RDBRDFVisitor(RDBContext context, Function<Long, NODE> transformer) {
this.context = context;
this.transformer = transformer;
}
private Long getId(NODE node) {
return context.getNodeId(node);
}
private Long getId(Constant<NODE> constant) {
return context.getNodeId(constant.getConstant());
}
private Expression<?> handle(Expression<?> expr, QueryMetadata md) {
return (Expression<?>) expr.accept(this, md);
}
private boolean needsSymbolResolving(Operation<?> op) {
if (Ops.equalsOps.contains(op.getOperator())
|| Ops.notEqualsOps.contains(op.getOperator())
|| op.getOperator() == Ops.IN
|| op.getOperator() == Ops.ORDINAL) {
for (Expression<?> arg : op.getArgs()) {
if (!(arg instanceof Path<?>)
&& !(arg instanceof ParamExpression<?>)
&& !(arg instanceof Constant<?>)) {
return true;
}
}
return false;
} else {
return true;
}
}
@SuppressWarnings("unchecked")
@Override
public Object visit(QueryMetadata md, QueryLanguage<?, ?> queryType) {
query = context.createQuery();
// FIXME
boolean asCount = md.getProjection().size() == 1 && md.getProjection().get(0).toString().equals("count(*)");
// where
handle(md.getWhere(), md);
// group by
for (Expression<?> gb : md.getGroupBy()) {
query.groupBy(handle(gb, md));
}
// having
if (md.getHaving() != null) {
query.having((Predicate) handle(md.getHaving(), md));
}
if (!asCount) {
// order by
asLiteral = true;
for (OrderSpecifier<?> order : md.getOrderBy()) {
query.orderBy(new OrderSpecifier(order.getOrder(), handle(order.getTarget(), md)));
}
asLiteral = false;
// limit + offset
query.restrict(md.getModifiers());
// distinct
if (md.isDistinct()) {
query.distinct();
}
}
// select
if (queryType.equals(QueryLanguage.TUPLE)) {
List<String> variables = new ArrayList<String>();
List<Expression<?>> pr = new ArrayList<Expression<?>>();
Collection<? extends Expression<?>> projection = md.getProjection();
if (projection.isEmpty()) {
projection = namedExpressions;
}
for (Expression<?> expr : projection) {
if (expr instanceof ParamExpression) {
variables.add(((ParamExpression) expr).getName());
} else {
variables.add(expr.toString());
}
if (resources.contains(expr) || ID.class.isAssignableFrom(expr.getType())) {
boolean asLit = asLiteral;
asLiteral = true;
pr.add(handle(expr, md));
asLiteral = asLit;
} else {
pr.add(handle(expr, md));
}
}
return new TupleQueryImpl((SQLQuery) query, context.getConverters(), variables, pr, transformer);
// construct
} else if (queryType.equals(QueryLanguage.GRAPH)) {
// TODO : add also support for larger patterns
if (md.getProjection().size() == 1 && md.getProjection().get(0) instanceof PatternBlock) {
List<Expression<?>> pr = new ArrayList<Expression<?>>();
PatternBlock pattern = (PatternBlock) md.getProjection().get(0);
for (Expression<?> expr : Arrays.asList(pattern.getSubject(), pattern.getPredicate(), pattern.getObject(), pattern.getContext())) {
if (expr != null && !(expr instanceof Constant)) {
if (resources.contains(expr) || ID.class.isAssignableFrom(expr.getType())) {
boolean asLit = asLiteral;
asLiteral = true;
pr.add(handle(expr, md));
asLiteral = asLit;
} else {
pr.add(handle(expr, md));
}
}
}
return new GraphQueryImpl((SQLQuery) query, pattern, pr, transformer);
} else {
throw new UnsupportedOperationException();
}
// ask
} else if (queryType.equals(QueryLanguage.BOOLEAN)) {
return new BooleanQueryImpl((SQLQuery) query);
} else {
throw new UnsupportedOperationException();
}
}
@SuppressWarnings("unchecked")
@Override
public Constant<?> visit(Constant<?> constant, QueryMetadata context) {
if (NODE.class.isAssignableFrom(constant.getType())) {
NODE node = (NODE) constant.getConstant();
if (asLiteral) {
return ConstantImpl.create(node.getValue());
} else {
return ConstantImpl.create(getId(node));
}
} else if (Collection.class.isAssignableFrom(constant.getType())) {
Collection<?> collection = (Collection<?>) constant.getConstant();
List<Object> rv = new ArrayList<Object>(collection.size());
for (Object o : collection) {
NODE node = (NODE) o;
if (asLiteral) {
rv.add(node.getValue());
} else {
rv.add(getId(node));
}
}
return new ConstantImpl<List>(List.class, rv);
} else if (String.class.equals(constant.getType())) {
if (asLiteral) {
return ConstantImpl.create(constant.getConstant().toString());
} else {
return ConstantImpl.create(getId(new LIT(constant.getConstant().toString())));
}
} else if (ConverterRegistryImpl.DEFAULT.supports(constant.getType())) {
String value = ConverterRegistryImpl.DEFAULT.toString(constant.getConstant());
if (asLiteral) {
return ConstantImpl.create(value);
} else {
UID datatype = ConverterRegistryImpl.DEFAULT.getDatatype(constant.getType());
return ConstantImpl.create(getId(new LIT(value, datatype)));
}
} else {
throw new IllegalArgumentException(constant.toString());
}
}
@Override
public Object visit(FactoryExpression<?> expr, QueryMetadata context) {
throw new UnsupportedOperationException();
}
@Override
public Object visit(GraphBlock expr, QueryMetadata context) {
resources.add(expr.getContext());
graphs.push(expr.getContext());
visit((ContainerBlock) expr, context);
graphs.pop();
return null;
}
@Override
public Object visit(GroupBlock expr, QueryMetadata context) {
return visit((ContainerBlock) expr, context);
}
@SuppressWarnings("unchecked")
@Override
public Object visit(Operation<?> expr, QueryMetadata context) {
List<Expression<?>> args = new ArrayList<Expression<?>>(expr.getArgs().size());
boolean asLit = asLiteral;
asLiteral = needsSymbolResolving(expr);
try {
operators.push(expr.getOperator());
if (expr.getOperator() == Ops.NUMCAST) {
args.add(handle(expr.getArg(0), context));
UID datatype = (UID) ((Constant) expr.getArg(1)).getConstant();
args.add(new ConstantImpl<Class>(this.context.getConverters().getClass(datatype)));
} else {
for (Expression<?> arg : expr.getArgs()) {
args.add(handle(arg, context));
}
}
} finally {
operators.pop();
}
asLiteral = asLit;
if (expr.getType().equals(Boolean.class)) {
return new PredicateOperation((Operator) expr.getOperator(), ImmutableList.copyOf(args));
} else {
return new OperationImpl(expr.getType(), expr.getOperator(), ImmutableList.copyOf(args));
}
}
@Override
public Object visit(OptionalBlock expr, QueryMetadata context) {
boolean inOpt = inOptional;
inOptional = true;
visit((ContainerBlock) expr, context);
inOptional = inOpt;
return null;
}
@Nullable
private Object visit(ContainerBlock expr, QueryMetadata context) {
for (Block block : expr.getBlocks()) {
handle(block, context);
}
if (expr.getFilters() != null) {
query.where((Predicate) handle(expr.getFilters(), context));
}
return null;
}
@Override
public Expression<?> visit(ParamExpression<?> expr, QueryMetadata context) {
namedExpressions.add(expr);
return visitPathOrParam(expr, expr.getName(), context);
}
@Override
public Expression<?> visit(Path<?> expr, QueryMetadata context) {
namedExpressions.add(expr);
return visitPathOrParam(expr, expr.toString(), context);
}
private Expression<?> visitPathOrParam(Expression<?> expr, String exprToString, QueryMetadata context) {
if (asLiteral) {
if (exprToSymbol.containsKey(expr)) {
return exprToSymbol.get(expr);
} else {
QSymbol symbol = new QSymbol(symbols.next());
query.leftJoin(symbol).on(symbol.id.eq(exprToMapped.get(expr)));
Expression<?> lexical = symbol.lexical;
if (inMathOperation()) {
lexical = symbol.floatval;
numericSymbols.add(symbol);
} else if (inDateOperation()) {
lexical = symbol.datetimeval;
dateTimeSymbols.add(symbol);
} else if (numericSymbols.contains(symbol)) {
lexical = symbol.floatval;
} else if (dateTimeSymbols.contains(symbol)) {
lexical = symbol.datetimeval;
}
exprToSymbol.put(expr, lexical);
return lexical;
}
} else {
return exprToMapped.get(expr);
}
}
private boolean inMathOperation() {
for (Operator<?> op : operators) {
if (MATH.contains(op)) {
return true;
}
}
return false;
}
private boolean inDateOperation() {
for (Operator<?> op : operators) {
if (DATE.contains(op)) {
return true;
}
}
return false;
}
@Override
public Object visit(PatternBlock expr, QueryMetadata context) {
QStatement stmt = new QStatement(stmts.next());
BooleanBuilder filters = new BooleanBuilder();
resources.add(expr.getSubject());
resources.add(expr.getPredicate());
if (ID.class.isAssignableFrom(expr.getObject().getType())) {
resources.add(expr.getObject());
}
if (expr.getContext() != null) {
resources.add(expr.getContext());
}
if (isNamed(expr.getSubject())) {
namedExpressions.add(expr.getSubject());
}
if (isNamed(expr.getPredicate())) {
namedExpressions.add(expr.getPredicate());
}
if (isNamed(expr.getObject())) {
namedExpressions.add(expr.getObject());
}
if (isNamed(expr.getContext())) {
namedExpressions.add(expr.getContext());
}
filters.and(visitPatternElement(context, stmt.subject, expr.getSubject()));
filters.and(visitPatternElement(context, stmt.predicate, expr.getPredicate()));
filters.and(visitPatternElement(context, stmt.object, expr.getObject()));
Expression<UID> c = expr.getContext();
if (c == null && !graphs.isEmpty()) {
c = graphs.peek();
}
if (c != null) {
filters.and(visitPatternElement(context, stmt.model, c));
}
if (firstSource) {
query.from(stmt).where(filters.getValue());
} else if (inOptional) {
query.leftJoin(stmt).on(filters.getValue());
} else {
query.innerJoin(stmt).on(filters.getValue());
}
firstSource = false;
return null;
}
private boolean isNamed(Expression<?> expr) {
return expr instanceof Path<?> || expr instanceof ParamExpression<?>;
}
@SuppressWarnings("unchecked")
@Nullable
private Predicate visitPatternElement(QueryMetadata context, NumberPath<Long> path, Expression<? extends NODE> s) {
if (s instanceof Constant<?>) {
return path.eq(getId((Constant) s));
} else {
if (exprToMapped.containsKey(s)) {
return path.eq(exprToMapped.get(s));
} else {
exprToMapped.put(s, path);
if (s instanceof ParamExpression<?>) {
Object constant = context.getParams().get(s);
if (constant != null) {
return path.eq(getId((NODE) constant));
}
}
}
}
return null;
}
@SuppressWarnings("unchecked")
@Override
public SubQueryExpression<?> visit(SubQueryExpression<?> expr, QueryMetadata context) {
SQLCommonQuery<?> q = query;
boolean firstS = firstSource;
firstSource = true;
query = new SQLSubQuery();
Map<Expression<?>, Path<Long>> exprToM = exprToMapped;
Map<Expression<?>, Expression<?>> exprToS = exprToSymbol;
exprToMapped = new HashMap<Expression<?>, Path<Long>>(exprToMapped);
exprToSymbol = new HashMap<Expression<?>, Expression<?>>(exprToSymbol);
// copy bindings from parent
for (Map.Entry<ParamExpression<?>, Object> entry : context.getParams().entrySet()) {
expr.getMetadata().setParam((ParamExpression) entry.getKey(), entry.getValue());
}
QueryMetadata md = expr.getMetadata();
// where
handle(md.getWhere(), md);
// group by
for (Expression<?> gb : md.getGroupBy()) {
query.groupBy(handle(gb, md));
}
// having
if (md.getHaving() != null) {
query.having((Predicate) handle(md.getHaving(), md));
}
// order by
boolean asLit = asLiteral;
asLiteral = true;
for (OrderSpecifier<?> order : md.getOrderBy()) {
query.orderBy(new OrderSpecifier(order.getOrder(), handle(order.getTarget(), md)));
}
asLiteral = asLit;
// limit + offset
query.restrict(md.getModifiers());
// distinct
if (md.isDistinct()) {
query.distinct();
}
List<Expression<?>> projection = new ArrayList<Expression<?>>();
for (Expression<?> e : md.getProjection()) {
projection.add(handle(e, md));
}
SubQueryExpression<?> sqe = ((SQLSubQuery) query).list(projection.toArray(new Expression[projection.size()]));
firstSource = firstS;
query = q;
exprToMapped = exprToM;
exprToSymbol = exprToS;
return sqe;
}
@Override
public Object visit(TemplateExpression<?> expr, QueryMetadata context) {
ImmutableList.Builder<Object> builder = ImmutableList.builder();
for (Object arg : expr.getArgs()) {
if (arg instanceof Expression) {
builder.add(handle((Expression) arg, context));
} else {
builder.add(arg);
}
}
if (expr.getType().equals(Boolean.class)) {
return new BooleanTemplate(expr.getTemplate(), builder.build());
} else {
return new TemplateExpressionImpl<Object>(expr.getType(), expr.getTemplate(), builder.build());
}
}
@Override
public Object visit(UnionBlock expr, QueryMetadata context) {
throw new UnsupportedOperationException();
}
}