/* * 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.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Stack; import org.teiid.language.*; import org.teiid.language.AndOr.Operator; import org.teiid.language.Update; import org.teiid.language.visitor.CollectorVisitor; import org.teiid.metadata.RuntimeMetadata; import org.teiid.translator.TranslatorException; import org.teiid.translator.mongodb.MergeDetails.Association; import org.teiid.translator.mongodb.MongoDBUpdateExecution.RowInfo; import com.mongodb.*; public class MongoDBUpdateVisitor extends MongoDBSelectVisitor { protected LinkedHashMap<String, Object> columnValues = new LinkedHashMap<String, Object>(); private DB mongoDB; private BasicDBObject pull; private Condition condition; protected Stack<DBObject> onGoingPullCriteria = new Stack<DBObject>(); protected TranslatorException pullException; public MongoDBUpdateVisitor(MongoDBExecutionFactory executionFactory, RuntimeMetadata metadata, DB mongoDB) { super(executionFactory, metadata); this.mongoDB = mongoDB; } @Override public void visit(Insert obj) { append(obj.getTable()); List<ColumnReference> columns = obj.getColumns(); List<Expression> values = ((ExpressionValueSource)obj.getValueSource()).getValues(); try { IDRef pk = null; for (int i = 0; i < columns.size(); i++) { String colName = getColumnName(columns.get(i)); Expression expr = values.get(i); Object value = resolveExpressionValue(colName, expr); if (this.mongoDoc.isPartOfPrimaryKey(colName)) { if (pk == null) { pk = new IDRef(); } pk.addColumn(colName, value); } else { this.columnValues.put(colName, value); } // Update he mongo document to keep track the reference values. this.mongoDoc.updateReferenceColumnValue(obj.getTable().getName(), colName, value); // if this FK column, replace with reference rather than simple key value if (this.mongoDoc.isPartOfForeignKey(colName)) { MergeDetails ref = this.mongoDoc.getFKReference(colName); this.columnValues.put(colName, ref.clone()); } } if (pk != null) { this.columnValues.put("_id", pk.getValue()); //$NON-NLS-1$ } } catch (TranslatorException e) { this.exceptions.add(e); } } private Object resolveExpressionValue(String colName, Expression expr) throws TranslatorException { Object value = null; if (expr instanceof Literal) { value = this.executionFactory.convertToMongoType(((Literal) expr).getValue(), this.mongoDB, colName); } else if (expr instanceof org.teiid.language.Array) { org.teiid.language.Array contents = (org.teiid.language.Array)expr; List<Expression> arrayExprs = contents.getExpressions(); value = new BasicDBList(); for (Expression exp:arrayExprs) { if (exp instanceof Literal) { ((BasicDBList)value).add(this.executionFactory.convertToMongoType(((Literal) exp).getValue(), this.mongoDB, colName)); } else { this.exceptions.add(new TranslatorException(MongoDBPlugin.Util.gs(MongoDBPlugin.Event.TEIID18001))); } } } else { this.exceptions.add(new TranslatorException(MongoDBPlugin.Util.gs(MongoDBPlugin.Event.TEIID18001))); } return value; } @Override public void visit(Update obj) { this.condition = obj.getWhere(); append(obj.getTable()); List<SetClause> changes = obj.getChanges(); try { IDRef pk = null; for (SetClause clause:changes) { String colName = getColumnName(clause.getSymbol()); // make sure user not updating the linked keys if (this.mongoDoc.isMerged()) { if (this.mongoDoc.getMergeKey().getAssociation() == Association.ONE && this.mongoDoc.isPartOfPrimaryKey(colName)) { throw new TranslatorException(MongoDBPlugin.Event.TEIID18035, MongoDBPlugin.Util.gs( MongoDBPlugin.Event.TEIID18035, colName, obj.getTable().getName())); } else if (this.mongoDoc.getMergeKey().getAssociation() == Association.MANY && this.mongoDoc.isPartOfForeignKey(colName)) { throw new TranslatorException(MongoDBPlugin.Event.TEIID18036, MongoDBPlugin.Util.gs( MongoDBPlugin.Event.TEIID18036, colName, obj.getTable().getName())); } } Expression expr = clause.getValue(); Object value = resolveExpressionValue(colName, expr); if (this.mongoDoc.isPartOfPrimaryKey(colName)) { if (pk == null) { pk = new IDRef(); } pk.addColumn(colName, value); } else { this.columnValues.put(colName, value); } // Update the mongo document to keep track the reference values. this.mongoDoc.updateReferenceColumnValue(obj.getTable().getName(), colName, value); // if this FK column, replace with reference rather than simple key value if (this.mongoDoc.isPartOfForeignKey(colName)) { MergeDetails ref = this.mongoDoc.getFKReference(colName); this.columnValues.put(colName, ref.clone()); } } if (pk != null) { this.columnValues.put("_id", pk.getValue()); //$NON-NLS-1$ } } catch (TranslatorException e) { this.exceptions.add(e); } append(obj.getWhere()); if (!this.onGoingExpression.isEmpty()) { this.match = (DBObject)this.onGoingExpression.pop(); } } @Override public void visit(Delete obj) { this.condition = obj.getWhere(); append(obj.getTable()); append(obj.getWhere()); if (!this.onGoingExpression.isEmpty()) { this.match = (DBObject)this.onGoingExpression.pop(); } } public BasicDBObject getInsert(LinkedHashMap<String, DBObject> embeddedDocuments) { BasicDBObject insert = new BasicDBObject(); for (String key:this.columnValues.keySet()) { Object obj = this.columnValues.get(key); if (obj instanceof MergeDetails) { obj = ((MergeDetails)obj).getValue(); } if (key.equals("_id")) { //$NON-NLS-1$ insert.append("_id", obj); //$NON-NLS-1$ } if (!this.mongoDoc.isPartOfPrimaryKey(key)) { if (this.mongoDoc.isPartOfForeignKey(key)) { if (obj instanceof BasicDBObject) { insert.append(key, ((BasicDBObject) obj).get(key)); } else { insert.append(key, obj); } } else { insert.append(key, obj); } } } if (this.mongoDoc.hasEmbeddedDocuments()) { for (String docName:this.mongoDoc.getEmbeddedDocumentNames()) { DBObject embedDoc = embeddedDocuments.get(docName); if (embedDoc != null) { insert.append(docName, embedDoc); } } } return insert; } public BasicDBObject getUpdate(LinkedHashMap<String, DBObject> embeddedDocuments) throws TranslatorException { BasicDBObject update = new BasicDBObject(); for (String key:this.columnValues.keySet()) { Object obj = this.columnValues.get(key); if (obj instanceof MergeDetails) { MergeDetails ref = ((MergeDetails)obj); if (this.mongoDoc.isMerged()) { // do not allow updating the main document reference where this embedded document is embedded. if (ref.getParentTable().equals(this.mongoDoc.getMergeTable().getName())) { throw new TranslatorException(MongoDBPlugin.Util.gs(MongoDBPlugin.Event.TEIID18007, ref.getParentTable(), this.mongoDoc.getDocumentName())); } } //update.append(key, ref.getDBRef(db, true)); if (this.mongoDoc.isPartOfForeignKey(key)) { if (ref.getValue() instanceof BasicDBObject) { update.append(key, ((BasicDBObject) ref.getValue()).get(key)); } else { update.append(key, ref.getValue()); } } else { update.append(key, ref.getValue()); } // also update the embedded document if (this.mongoDoc.hasEmbeddedDocuments()) { for (MergeDetails docKey: this.mongoDoc.getEmbeddedReferences()) { if (ref.getParentTable().equals(docKey.getEmbeddedTable())) { DBObject embedDoc = embeddedDocuments.get(docKey.getName()); if (embedDoc == null || ref.getValue() == null) { update.append(docKey.getName(), null); } else { update.append(docKey.getName(), embedDoc); } } } } } else { if (this.mongoDoc.isMerged()) { if (this.mongoDoc.getMergeAssociation() == Association.MANY) { update.append(this.mongoDoc.getDocumentName()+".$."+key, obj); //$NON-NLS-1$ } else { update.append(this.mongoDoc.getDocumentName()+"."+key, obj); //$NON-NLS-1$ } } else { if (this.mongoDoc.isPartOfPrimaryKey(key)) { if (hasCompositePrimaryKey(this.mongoDoc.getTargetTable())) { update.append("_id."+key, obj);//$NON-NLS-1$ } else { update.append("_id", obj); //$NON-NLS-1$ } } else { update.append(key, obj); } } } } return update; } public BasicDBObject getPullQuery() throws TranslatorException { if (this.pullException != null) { throw this.pullException; } if (this.pull == null) { if (this.onGoingPullCriteria.isEmpty()) { this.pull = new BasicDBObject(); } else { this.pull = new BasicDBObject(this.mongoDoc.getTable().getName(), this.onGoingPullCriteria.pop()); } } return this.pull; } public boolean updateMerge(BasicDBList previousRows, RowInfo parentKey, BasicDBList updated) throws TranslatorException { boolean update = false; for (int i = 0; i < previousRows.size(); i++) { BasicDBObject row = (BasicDBObject)previousRows.get(i); if (this.match == null && getPullQuery() == null || ExpressionEvaluator.matches(this.executionFactory, this.mongoDB, this.condition, row, parentKey)) { update = true; for (String key:this.columnValues.keySet()) { Object obj = this.columnValues.get(key); if (obj instanceof MergeDetails) { MergeDetails ref = ((MergeDetails)obj); row.put(key, ref.getValue()); } else { row.put(key, obj); } } } updated.add(row); } return update; } public boolean updateDelete(BasicDBList previousRows, RowInfo parentKey, BasicDBList updated) throws TranslatorException { for (int i = 0; i < previousRows.size(); i++) { BasicDBObject row = (BasicDBObject)previousRows.get(i); if (this.match == null && getPullQuery() == null || ExpressionEvaluator.matches(this.executionFactory, this.mongoDB, this.condition, row, parentKey)) { //do not add } else { updated.add(row); } } return updated.size() != previousRows.size(); } public boolean updateMerge(BasicDBObject previousRow, RowInfo parentKey) throws TranslatorException { boolean update = false; if (this.match == null || ExpressionEvaluator.matches(this.executionFactory, this.mongoDB, this.condition, previousRow, parentKey)) { for (String key:this.columnValues.keySet()) { Object obj = this.columnValues.get(key); update = true; if (obj instanceof MergeDetails) { MergeDetails ref = ((MergeDetails)obj); previousRow.put(key, ref.getValue()); } else { previousRow.put(key, obj); } } } return update; } @Override public void visit(Comparison obj) { if (!this.mongoDoc.isMerged() || this.mongoDoc.isMerged() && this.mongoDoc.getMergeAssociation() != Association.MANY) { super.visit(obj); return; } try { // this for the normal where clause ColumnDetail leftExpr = getExpressionAlias(obj.getLeftExpression()); append(obj.getRightExpression()); Object rightExpr = this.onGoingExpression.pop(); if (this.expressionMap.get(rightExpr) != null) { rightExpr = this.expressionMap.get(rightExpr).getProjectedName(); } // build pull criteria for delete; the pull criteria only applies in merge scenario // and only columns in the embedded document. boolean buildPullQuery = (includeInPullCriteria(obj.getLeftExpression()) && includeInPullCriteria(obj.getRightExpression())); if (!buildPullQuery) { QueryBuilder query = leftExpr.getQueryBuilder(); buildComparisionQuery(obj, rightExpr, query); this.onGoingExpression.push(query.get()); } else { QueryBuilder pullQuery = leftExpr.getPullQueryBuilder(); buildComparisionQuery(obj, rightExpr, pullQuery); this.onGoingPullCriteria.push(pullQuery.get()); } if (obj.getLeftExpression() instanceof ColumnReference) { ColumnReference column = (ColumnReference)obj.getLeftExpression(); this.mongoDoc.updateReferenceColumnValue(column.getTable().getName(), column.getName(), rightExpr); } } catch (TranslatorException e) { this.exceptions.add(e); } } private boolean includeInPullCriteria(Expression expr) throws TranslatorException { if (!this.mongoDoc.isMerged()) { return false; } Collection<ColumnReference> columns = CollectorVisitor.collectElements(expr); for (ColumnReference column:columns) { if (this.mongoDoc.isPartOfForeignKey(column.getName())) { return false; } } return true; } @Override public void visit(AndOr obj) { if (!this.mongoDoc.isMerged() || this.mongoDoc.isMerged() && this.mongoDoc.getMergeAssociation() != Association.MANY) { super.visit(obj); return; } append(obj.getLeftCondition()); append(obj.getRightCondition()); boolean valid = false; if (this.onGoingExpression.size() >= 2) { DBObject right = (DBObject)this.onGoingExpression.pop(); DBObject left = (DBObject) this.onGoingExpression.pop(); switch(obj.getOperator()) { case AND: this.onGoingExpression.push(QueryBuilder.start().and(left, right).get()); break; case OR: this.onGoingExpression.push(QueryBuilder.start().or(left, right).get()); break; } valid = true; } if (this.onGoingPullCriteria.size() >= 2) { DBObject pullRight = this.onGoingPullCriteria.pop(); DBObject pullLeft = this.onGoingPullCriteria.pop(); switch(obj.getOperator()) { case AND: this.onGoingPullCriteria.push(QueryBuilder.start().and(pullLeft, pullRight).get()); break; case OR: this.onGoingPullCriteria.push(QueryBuilder.start().or(pullLeft, pullRight).get()); break; } valid = true; } if (!valid && obj.getOperator() == Operator.OR) { this.pullException = new TranslatorException(MongoDBPlugin.Event.TEIID18029, MongoDBPlugin.Util.gs(MongoDBPlugin.Event.TEIID18029)); } } @Override public void visit(Function obj) { if (!this.mongoDoc.isMerged() || this.mongoDoc.isMerged() && this.mongoDoc.getMergeAssociation() != Association.MANY) { super.visit(obj); return; } this.pullException = new TranslatorException(MongoDBPlugin.Event.TEIID18028, MongoDBPlugin.Util.gs(MongoDBPlugin.Event.TEIID18028)); } @Override public void visit(In obj) { if (!this.mongoDoc.isMerged() || this.mongoDoc.isMerged() && this.mongoDoc.getMergeAssociation() != Association.MANY) { super.visit(obj); return; } try { boolean buildPullQuery = includeInPullCriteria(obj.getLeftExpression()); if (buildPullQuery) { ColumnDetail exprAlias = getExpressionAlias(obj.getLeftExpression()); this.onGoingPullCriteria.push(buildInQuery(obj, exprAlias.getPullQueryBuilder()).get()); } else { ColumnDetail exprAlias = getExpressionAlias(obj.getLeftExpression()); this.onGoingExpression.push(buildInQuery(obj, exprAlias.getQueryBuilder()).get()); } } catch (TranslatorException e) { this.exceptions.add(e); } } @Override public void visit(IsNull obj) { if (!this.mongoDoc.isMerged() || this.mongoDoc.isMerged() && this.mongoDoc.getMergeAssociation() != Association.MANY) { super.visit(obj); return; } try { boolean buildPullQuery = includeInPullCriteria(obj.getExpression()); if (buildPullQuery) { ColumnDetail exprAlias = getExpressionAlias(obj.getExpression()); this.onGoingPullCriteria.push(buildIsNullQuery(obj, exprAlias.getPullQueryBuilder()).get()); } else { ColumnDetail exprAlias = getExpressionAlias(obj.getExpression()); this.onGoingExpression.push(buildIsNullQuery(obj, exprAlias.getQueryBuilder()).get()); } } catch (TranslatorException e) { this.exceptions.add(e); } } @Override public void visit(Like obj) { if (!this.mongoDoc.isMerged() || this.mongoDoc.isMerged() && this.mongoDoc.getMergeAssociation() != Association.MANY) { super.visit(obj); return; } try { boolean buildPullQuery = includeInPullCriteria(obj.getLeftExpression()); if (buildPullQuery) { ColumnDetail exprAlias = getExpressionAlias(obj.getLeftExpression()); this.onGoingPullCriteria.push(buildLikeQuery(obj, exprAlias.getPullQueryBuilder()).get()); } else { ColumnDetail exprAlias = getExpressionAlias(obj.getLeftExpression()); this.onGoingExpression.push(buildLikeQuery(obj, exprAlias.getQueryBuilder()).get()); } } catch (TranslatorException e) { this.exceptions.add(e); } } }