package org.teiid.translator.salesforce.execution.visitors; import org.teiid.core.util.StringUtil; import org.teiid.language.*; import org.teiid.language.Join.JoinType; import org.teiid.metadata.Column; import org.teiid.metadata.ForeignKey; import org.teiid.metadata.RuntimeMetadata; import org.teiid.metadata.Table; import org.teiid.translator.TranslatorException; /** * Salesforce supports joins only on primary key/foreign key relationships. The connector * is supporting these joins through the OUTER JOIN syntax. All RIGHT OUTER JOINS are * rewritten by the query processor as LEFT OUTER JOINS, so that is all this visitor has * to expect. * * Salesforce also requires a different syntax depending upon if you are joining from parent * to child, or from child to parent. * http://www.salesforce.com/us/developer/docs/api/index_Left.htm#StartTopic=Content/sforce_api_calls_soql_relationships.htm * */ public class JoinQueryVisitor extends SelectVisitor { private Table leftTableInJoin; private Table rightTableInJoin; private Table childTable; private String parentName; private ForeignKey foreignKey; public JoinQueryVisitor(RuntimeMetadata metadata) { super(metadata); } // Has to be a left outer join of only 2 tables. criteria must be a compare @Override public void visit(Join join) { try { TableReference left = join.getLeftItem(); NamedTable leftGroup = (NamedTable) left; leftTableInJoin = leftGroup.getMetadataObject(); loadColumnMetadata(leftGroup); TableReference right = join.getRightItem(); NamedTable rightGroup = (NamedTable) right; rightTableInJoin = rightGroup.getMetadataObject(); loadColumnMetadata((NamedTable) right); Comparison criteria = (Comparison) join.getCondition(); Expression lExp = criteria.getLeftExpression(); Expression rExp = criteria.getRightExpression(); if (isIdColumn(rExp) || isIdColumn(lExp)) { Column rColumn = ((ColumnReference) rExp).getMetadataObject(); String rTableName = rColumn.getParent().getSourceName(); Column lColumn = ((ColumnReference) lExp).getMetadataObject(); String lTableName = lColumn.getParent().getSourceName(); if (leftTableInJoin.getSourceName().equals(rTableName) || leftTableInJoin.getSourceName().equals(lTableName) && rightTableInJoin.getSourceName().equals(rTableName) || rightTableInJoin.getSourceName().equals(lTableName) && !rTableName.equals(lTableName)) { // This is the join criteria, the one that is the ID is the parent. Expression fKey = !isIdColumn(lExp) ? lExp : rExp; ColumnReference columnReference = (ColumnReference) fKey; table = childTable = (Table)columnReference.getMetadataObject().getParent(); String name = columnReference.getMetadataObject().getSourceName(); if (StringUtil.endsWithIgnoreCase(name, "id")) { this.parentName = name.substring(0, name.length() - 2); } Table parent = leftTableInJoin; if (isChildToParentJoin()) { parent = rightTableInJoin; } for (ForeignKey fk : childTable.getForeignKeys()) { if (fk.getReferenceKey().equals(parent.getPrimaryKey())) { foreignKey = fk; break; } } //inner joins require special handling as relationship queries are outer by default if (join.getJoinType() == JoinType.INNER_JOIN) { if (!isChildToParentJoin()) { //flip the relationship Table t = leftTableInJoin; this.leftTableInJoin = rightTableInJoin; this.rightTableInJoin = t; } //add is null criteria addCriteria(new Comparison(fKey, new Literal(null, fKey.getType()), Comparison.Operator.NE)); } } else { // Only add the criteria to the query if it is not the join criteria. // The join criteria is implicit in the salesforce syntax. super.visit(criteria); //TODO: not valid } } else { super.visit(criteria); //TODO: not valid } } catch (TranslatorException ce) { exceptions.add(ce); } } @Override public String getQuery() throws TranslatorException { if (isChildToParentJoin()) { return super.getQuery(); } if (!exceptions.isEmpty()) { throw exceptions.get(0); } StringBuilder select = new StringBuilder(); select.append(SELECT).append(SPACE); addSelect(leftTableInJoin.getSourceName(), select, true); select.append(OPEN); StringBuilder subselect = new StringBuilder(); subselect.append(SELECT).append(SPACE); addSelect(rightTableInJoin.getSourceName(), subselect, false); subselect.append(SPACE); subselect.append(FROM).append(SPACE); String pluralName = null; if (this.foreignKey != null && this.foreignKey.getNameInSource() != null) { pluralName = this.foreignKey.getNameInSource(); } else { pluralName = rightTableInJoin.getNameInSource() + "s"; //$NON-NLS-1$ } subselect.append(pluralName); subselect.append(CLOSE).append(SPACE); select.append(subselect); select.append(FROM).append(SPACE); select.append(leftTableInJoin.getSourceName()).append(SPACE); addCriteriaString(select); appendGroupByHaving(select); select.append(limitClause); return select.toString(); } @Override void appendColumnReference(StringBuilder queryString, ColumnReference ref) { if (isChildToParentJoin() && this.rightTableInJoin.equals(ref.getMetadataObject().getParent()) && this.parentName != null) { //TODO: a self join won't work with this logic queryString.append(parentName); queryString.append('.'); queryString.append(ref.getMetadataObject().getSourceName()); } else { super.appendColumnReference(queryString, ref); } } public boolean isChildToParentJoin() { return childTable.equals(leftTableInJoin); } void addSelect(String tableNameInSource, StringBuilder result, boolean addComma) { boolean firstTime = true; for (DerivedColumn symbol : selectSymbols) { Expression expression = symbol.getExpression(); if (expression instanceof ColumnReference) { Column element = ((ColumnReference) expression).getMetadataObject(); String tableName = element.getParent().getSourceName(); if(!isChildToParentJoin() && !tableNameInSource.equals(tableName)) { continue; } if (!firstTime) { result.append(", "); //$NON-NLS-1$ } else { firstTime = false; } appendColumnReference(result, (ColumnReference) expression); } else if (expression instanceof AggregateFunction) { if (!firstTime) { result.append(", "); //$NON-NLS-1$ } else { firstTime = false; } appendAggregateFunction(result, (AggregateFunction)expression); } else { throw new AssertionError("Unknown select symbol type " + symbol); //$NON-NLS-1$ } } if (!firstTime && addComma) { result.append(", "); //$NON-NLS-1$ } } @Override public boolean canRetrieve() { return false; } }