/* * 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.translator.mongodb; import java.util.ArrayList; import org.teiid.language.Comparison; import org.teiid.language.Condition; import org.teiid.language.Join.JoinType; import org.teiid.language.visitor.HierarchyVisitor; import org.teiid.metadata.Column; import org.teiid.metadata.Table; import org.teiid.translator.TranslatorException; import org.teiid.translator.mongodb.MergeDetails.Association; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; class JoinCriteriaVisitor extends HierarchyVisitor { private JoinType joinType; private MongoDocument left; private MongoDocument right; protected ArrayList<TranslatorException> exceptions = new ArrayList<TranslatorException>(); protected MergePlanner mergePlanner; private String aliasName; public JoinCriteriaVisitor(JoinType joinType, MongoDocument left, MongoDocument right, MergePlanner mergePlanner) { this.joinType = joinType; this.left = left; this.right = right; this.mergePlanner = mergePlanner; } public void process(Condition condition) throws TranslatorException { if (condition != null) { visitNode(condition); } if (!this.exceptions.isEmpty()) { throw this.exceptions.get(0); } } @Override public void visit(Comparison obj) { try { // left outer join we do not need to any thing, if left is the parent, if right is parent then it // it is similar to right outer join which is not supported // inner join needs "exists" on embedded doc switch(obj.getOperator()) { case EQ: if (this.joinType.equals(JoinType.LEFT_OUTER_JOIN)) { if (left.contains(right)) { // if nesting is simple flat hierary then there is nothing to be done. However if // document is array is $unwind behavior is strange, it does not include the document // that has empty or null child document. So, we need to simulate such that there is // some nested doc using "$ifnull" if (this.right.isMerged() && this.right.getMergeAssociation().equals(Association.MANY)) { this.mergePlanner.addNode(new ProjectionNode(this.left, buildIfNullBasedProjection(this.left, this.right)), this.aliasName); } } else { // right is parent; left is child. However, left does not exist with out its parent // so in "MERGE" scenario, this is equal to a INNER JOIN if (left.isMerged()) { this.mergePlanner.addNode(new ExistsNode(this.left)); } else { //so, right is parent, now this is un-supported throw new TranslatorException(MongoDBPlugin.Util.gs(MongoDBPlugin.Event.TEIID18022, right.getTable().getName(), left.getTable().getName())); } } } else if (this.joinType.equals(JoinType.INNER_JOIN)){ if (this.left.contains(this.right)) { this.mergePlanner.addNode(new ExistsNode(this.right)); } else { this.mergePlanner.addNode(new ExistsNode(this.left)); } } else if (this.joinType.equals(JoinType.CROSS_JOIN) || this.joinType.equals(JoinType.FULL_OUTER_JOIN)){ throw new TranslatorException(MongoDBPlugin.Util.gs(MongoDBPlugin.Event.TEIID18022, left.getTable().getName(), right.getTable().getName(), this.joinType)); } break; case NE: case LT: case LE: case GT: case GE: this.exceptions.add(new TranslatorException(MongoDBPlugin.Util.gs(MongoDBPlugin.Event.TEIID18023))); break; } } catch (TranslatorException e) { this.exceptions.add(e); } } private BasicDBObject buildIfNullBasedProjection(MongoDocument parent, MongoDocument child) throws TranslatorException { BasicDBObject columns = new BasicDBObject(); Table table = parent.getTable(); for (Column c:table.getColumns()) { if (parent.isMerged() || parent.isEmbeddable()) { columns.append(parent.getQualifiedName(false)+"."+c.getName(), 1); //$NON-NLS-1$ } else { columns.append(c.getName(), 1); } } BasicDBList exprs = new BasicDBList(); exprs.add("$"+child.getQualifiedName(false)); //$NON-NLS-1$ BasicDBList list = new BasicDBList(); list.add(new BasicDBObject()); exprs.add(list); BasicDBObject ifnull = new BasicDBObject("$ifNull", exprs); //$NON-NLS-1$ this.aliasName = "__NN_"+child.getTable().getName();//$NON-NLS-1$ columns.append(this.aliasName, ifnull); child.setAlias(this.aliasName); child.getMergeKey().setAlias(this.aliasName); return columns; } }