org.teiid.translator.mongodb.MongoDBUpdateExecution.java Source code

Java tutorial

Introduction

Here is the source code for org.teiid.translator.mongodb.MongoDBUpdateExecution.java

Source

/*
 * 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 static org.teiid.language.visitor.SQLStringVisitor.getRecordName;

import java.util.*;

import org.teiid.GeneratedKeys;
import org.teiid.language.Command;
import org.teiid.language.Insert;
import org.teiid.language.Update;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.metadata.*;
import org.teiid.mongodb.MongoDBConnection;
import org.teiid.translator.DataNotAvailableException;
import org.teiid.translator.ExecutionContext;
import org.teiid.translator.TranslatorException;
import org.teiid.translator.UpdateExecution;
import org.teiid.translator.mongodb.MergeDetails.Association;

import com.mongodb.*;

public class MongoDBUpdateExecution extends MongoDBBaseExecution implements UpdateExecution {
    private Command command;
    private MongoDBUpdateVisitor visitor;
    private MongoDBExecutionFactory executionFactory;
    private int[] results = new int[] { 0 };

    public MongoDBUpdateExecution(MongoDBExecutionFactory executionFactory, Command command,
            ExecutionContext executionContext, RuntimeMetadata metadata, MongoDBConnection connection)
            throws TranslatorException {
        super(executionContext, metadata, connection);
        this.command = command;
        this.executionFactory = executionFactory;

        this.visitor = new MongoDBUpdateVisitor(executionFactory, metadata, this.mongoDB);
        this.visitor.visitNode(command);

        if (!this.visitor.exceptions.isEmpty()) {
            throw this.visitor.exceptions.get(0);
        }
    }

    @Override
    public void close() {
    }

    @Override
    public void cancel() throws TranslatorException {
    }

    @Override
    public void execute() throws TranslatorException {
        try {
            executeInternal();
        } catch (MongoException e) {
            throw new TranslatorException(e);
        }
    }

    private void executeInternal() throws TranslatorException {

        DBCollection collection = getCollection(this.visitor.mongoDoc.getTargetTable());
        MongoDocument mongoDoc = this.visitor.mongoDoc;
        AggregationOptions options = this.executionFactory.getOptions(this.executionContext.getBatchSize());

        List<WriteResult> executionResults = new ArrayList<WriteResult>();

        if (this.command instanceof Insert) {
            // get pull key based documents to embed
            LinkedHashMap<String, DBObject> embeddedDocuments = fetchEmbeddedDocuments();

            // check if this document need to be embedded in any other document
            if (mongoDoc.isMerged()) {
                DBObject match = getInsertMatch(mongoDoc, this.visitor.columnValues);
                BasicDBObject insert = this.visitor.getInsert(embeddedDocuments);

                if (mongoDoc.getMergeKey().getAssociation() == Association.MANY) {
                    removeParentKey(mongoDoc, insert);
                    BasicDBObject insertDoc = new BasicDBObject(mongoDoc.getQualifiedName(true), insert);
                    LogManager.logDetail(LogConstants.CTX_CONNECTOR, "update - {\"$match\": {" + match + "}}"); //$NON-NLS-1$ //$NON-NLS-2$
                    LogManager.logDetail(LogConstants.CTX_CONNECTOR, "update - {\"$push\": {" + insertDoc + "}}"); //$NON-NLS-1$ //$NON-NLS-2$
                    executionResults.add(collection.update(match, new BasicDBObject("$push", insertDoc), false, //$NON-NLS-1$
                            true, WriteConcern.ACKNOWLEDGED));
                } else {
                    insert.remove("_id"); //$NON-NLS-1$
                    BasicDBObject insertDoc = new BasicDBObject(mongoDoc.getQualifiedName(true), insert);
                    LogManager.logDetail(LogConstants.CTX_CONNECTOR, "update - {\"$match\": {" + match + "}}"); //$NON-NLS-1$ //$NON-NLS-2$
                    LogManager.logDetail(LogConstants.CTX_CONNECTOR, "update - {\"$set\": {" + insertDoc + "}}"); //$NON-NLS-1$ //$NON-NLS-2$
                    executionResults.add(collection.update(match, new BasicDBObject("$set", insertDoc), false, true, //$NON-NLS-1$
                            WriteConcern.ACKNOWLEDGED));
                }
            } else {
                for (String docName : embeddedDocuments.keySet()) {
                    DBObject embeddedDoc = embeddedDocuments.get(docName);
                    embeddedDoc.removeField("_id"); //$NON-NLS-1$
                }
                // gets its own collection
                BasicDBObject in = this.visitor.getInsert(embeddedDocuments);
                LogManager.logDetail(LogConstants.CTX_CONNECTOR, "{\"insert\": {" + in + "}}"); //$NON-NLS-1$ //$NON-NLS-2$
                executionResults.add(collection.insert(in, WriteConcern.ACKNOWLEDGED));
            }
        } else if (this.command instanceof Update) {
            // get pull key based documents to embed
            LinkedHashMap<String, DBObject> embeddedDocuments = fetchEmbeddedDocuments();
            DBObject match = new BasicDBObject();
            if (this.visitor.match != null) {
                match = this.visitor.match;
            }
            if (mongoDoc.isMerged()) {
                // multi items in array update not available, http://jira.mongodb.org/browse/SERVER-1243
                // this work-around for above issue
                List<String> parentKeyNames = parentKeyNames(mongoDoc);

                DBObject documentMatch = new BasicDBObject("$match", match); //$NON-NLS-1$                  
                DBObject projection = new BasicDBObject("$project", buildProjectForUpdate(mongoDoc)); //$NON-NLS-1$
                Cursor output = collection.aggregate(Arrays.asList(documentMatch, projection), options);
                while (output.hasNext()) {
                    BasicDBObject row = (BasicDBObject) output.next();
                    buildUpdate(mongoDoc, collection, row, parentKeyNames, 0, null, executionResults,
                            new UpdateOperationImpl());
                }
            } else {
                for (String docName : embeddedDocuments.keySet()) {
                    DBObject embeddedDoc = embeddedDocuments.get(docName);
                    embeddedDoc.removeField("_id"); //$NON-NLS-1$
                }
                BasicDBObject u = this.visitor.getUpdate(embeddedDocuments);
                LogManager.logDetail(LogConstants.CTX_CONNECTOR, "update - {\"$match\": {" + match + "}}"); //$NON-NLS-1$ //$NON-NLS-2$
                LogManager.logDetail(LogConstants.CTX_CONNECTOR, "update - {\"$set\": {" + u + "}}"); //$NON-NLS-1$ //$NON-NLS-2$
                executionResults.add(collection.update(match, new BasicDBObject("$set", u), false, true, //$NON-NLS-1$
                        WriteConcern.ACKNOWLEDGED));
            }

            // if the update is for the "embeddable" table, then since it is copied to other tables
            // those references need to be updated. I know this is not atomic operation, but not sure
            // how else to handle it.
            if (mongoDoc.isEmbeddable()) {
                updateReferenceTables(collection, mongoDoc, match, options);
            }
        } else {
            // Delete
            DBObject match = new BasicDBObject();
            if (this.visitor.match != null) {
                match = this.visitor.match;
            }

            if (mongoDoc.isEmbeddable()) {
                DBObject m = new BasicDBObject("$match", match); //$NON-NLS-1$
                Cursor output = collection.aggregate(Arrays.asList(m), options);
                while (output.hasNext()) {
                    DBObject row = output.next();
                    if (row != null) {
                        for (MergeDetails ref : mongoDoc.getEmbeddedIntoReferences()) {
                            DBCollection parent = getCollection(ref.getParentTable());
                            DBObject parentMatch = buildParentMatch(row, ref);
                            DBObject refMatch = new BasicDBObject("$match", parentMatch); //$NON-NLS-1$
                            Cursor referenceOutput = parent.aggregate(Arrays.asList(refMatch), options);
                            if (referenceOutput.hasNext()) {
                                throw new TranslatorException(MongoDBPlugin.Util.gs(MongoDBPlugin.Event.TEIID18010,
                                        this.visitor.mongoDoc.getTargetTable().getName(), ref.getParentTable()));
                            }
                        }
                    }
                }
            }

            if (mongoDoc.isMerged()) {
                List<String> parentKeyNames = parentKeyNames(mongoDoc);

                DBObject documentMatch = new BasicDBObject("$match", match); //$NON-NLS-1$                      
                DBObject projection = new BasicDBObject("$project", buildProjectForUpdate(mongoDoc)); //$NON-NLS-1$
                Cursor output = collection.aggregate(Arrays.asList(documentMatch, projection), options);
                while (output.hasNext()) {
                    BasicDBObject row = (BasicDBObject) output.next();
                    buildUpdate(mongoDoc, collection, row, parentKeyNames, 0, null, executionResults,
                            new DeleteOperationImpl(match));
                }
            } else {
                LogManager.logDetail(LogConstants.CTX_CONNECTOR, "remove - {\"$match\": {" + match + "}}"); //$NON-NLS-1$ //$NON-NLS-2$
                executionResults.add(collection.remove(match, WriteConcern.ACKNOWLEDGED));
            }
        }

        if (!executionResults.isEmpty()) {
            if (this.command instanceof Insert) {
                if (this.executionContext.getCommandContext().isReturnAutoGeneratedKeys()) {
                    addAutoGeneretedKeys(executionResults.get(0));
                }
            }

            int updated = 0;
            for (WriteResult result : executionResults) {
                updated += result.getN();
            }

            this.results = new int[1];
            this.results[0] = updated;
        }
    }

    DBObject getInsertMatch(MongoDocument mongoDocument, Map<String, Object> values) throws TranslatorException {
        List<DBObject> matches = new ArrayList<DBObject>();
        HashMap<String, Object> matchValues = new HashMap<String, Object>();
        MongoDocument mergeDocument = mongoDocument.getMergeDocument();
        MongoDocument targetDocument = mongoDocument.getTargetDocument();

        if (mongoDocument.getMergeKey().getAssociation() == Association.ONE) {
            while (mergeDocument.isMerged()) {
                matches.add(QueryBuilder.start(mergeDocument.getDocumentName()).exists(true).get());
                if (mergeDocument.getMergeKey().getAssociation() == Association.ONE) {
                    mergeDocument = mergeDocument.getMergeDocument();
                } else {
                    break;
                }
            }
            if (mergeDocument.equals(targetDocument)) {
                matchValues.put("_id", values.get("_id")); //$NON-NLS-1$ //$NON-NLS-2$
            } else {
                matchValues.put(mergeDocument + "._id", values.get("_id")); //$NON-NLS-1$ //$NON-NLS-2$
            }
        } else {
            // this nested so add exists
            if (!mergeDocument.equals(targetDocument)) {
                matches.add(QueryBuilder.start(mergeDocument.getDocumentName()).exists(true).get());
            }

            // this value can go only one level up
            String columnName = mergeDocument
                    .getColumnName(mongoDocument.getMergeKey().getReferenceColumns().get(0));
            Object value = values.get(mongoDocument.getMergeKey().getColumns().get(0));
            if (value instanceof MergeDetails) {
                value = ((MergeDetails) value).getValue();
            }

            if (mergeDocument.equals(targetDocument)) {
                matchValues.put(columnName, value);
            } else {
                while (mergeDocument.isMerged()) {
                    if (mergeDocument.getMergeKey().getAssociation() == Association.ONE) {
                        mergeDocument = mergeDocument.getMergeDocument();
                    } else {
                        break;
                    }
                }
                matchValues.put(columnName, value);
            }
        }

        // build match
        BasicDBObject match = new BasicDBObject();
        for (String key : matchValues.keySet()) {
            match.append(key, matchValues.get(key));
        }
        if (!matches.isEmpty()) {
            matches.add(match);
            QueryBuilder qb = QueryBuilder.start().and(matches.toArray(new BasicDBObject[matches.size()]));
            match = (BasicDBObject) qb.get();
        }
        return match;
    }

    static class RowInfo {
        String tableName;
        String mergedTableName;
        Object PK;
        int rowNumber;
        RowInfo parent;
        boolean istop;

        static RowInfo build(String name, String mergeName, Object pk, int rowNumber, RowInfo parent) {
            RowInfo info = new RowInfo();
            info.tableName = name;
            info.mergedTableName = mergeName;
            info.PK = pk;
            info.rowNumber = rowNumber;
            if (parent != null) {
                info.parent = parent;
                info.istop = false;
            } else {
                info.istop = true;
            }
            return info;
        }

        public String getId(MongoDocument parent) {
            StringBuilder sb = new StringBuilder();
            sb.append(parent.getTable().getName());
            if (this.parent != null) {
                getId(sb);
            }
            return sb.toString();
        }

        private void getId(StringBuilder sb) {
            if (this.parent != null) {
                if (this.rowNumber != -1) {
                    sb.insert(0, "."); //$NON-NLS-1$
                    sb.insert(0, this.rowNumber);
                }
                this.parent.getId(sb);
            }
            if (!this.istop) {
                sb.insert(0, "."); //$NON-NLS-1$
                sb.insert(0, this.tableName);
            }
        }
    }

    private interface UpdateOperation {
        void execute(MongoDocument doc, DBCollection collection, DBObject row, DBObject dataRow, RowInfo rowInfo,
                List<WriteResult> executionResults) throws TranslatorException;
    }

    private void buildUpdate(MongoDocument doc, DBCollection collection, BasicDBObject row, List<String> parentKeys,
            int level, RowInfo rowInfo, List<WriteResult> executionResults, UpdateOperation operation)
            throws TranslatorException {

        String parentKeyName = parentKeys.get(level);
        boolean top = parentKeyName.equals(doc.getTargetDocument().getQualifiedName(false));
        Object parentBlock = row.get(top ? "_id" : parentKeyName); //$NON-NLS-1$

        // the parent-child must have been one-2-one relationship 
        if (parentBlock == null) {
            parentBlock = rowInfo.PK;
        }
        String mergeTableName = doc.getTable().getName();
        if (parentKeys.size() != (level + 1)) {
            mergeTableName = parentKeys.get(level + 1);
        }

        if (parentBlock instanceof BasicDBList) {
            // so parent is an array document
            BasicDBList parentRows = (BasicDBList) parentBlock;
            //parentRows = (BasicDBList)((BasicDBObject)parentRows.get(0)).get("_id"); //$NON-NLS-1$
            for (int i = 0; i < parentRows.size(); i++) {
                RowInfo info = RowInfo.build(parentKeyName, mergeTableName, parentRows.get(i), i, rowInfo);
                if (parentKeys.size() == (level + 1)) {
                    String aliasDocumentName = doc.getQualifiedName(false).replace('.', '_');
                    BasicDBList dataRows = (BasicDBList) row.get(aliasDocumentName);
                    if (dataRows != null && dataRows.size() > i) {
                        operation.execute(doc, collection, row, (DBObject) dataRows.get(i), info, executionResults);
                    }
                } else {
                    buildUpdate(doc, collection, row, parentKeys, level + 1, info, executionResults, operation);
                }
            }
        } else {
            // here the _id is same as parent
            RowInfo info = RowInfo.build(parentKeyName, mergeTableName, parentBlock, -1, rowInfo);
            if (parentKeys.size() == (level + 1)) {
                //Leaf, no more down
                String aliasDocumentName = doc.getQualifiedName(false).replace('.', '_');
                DBObject dataRows = (DBObject) row.get(aliasDocumentName);
                if (dataRows != null) {
                    operation.execute(doc, collection, row, dataRows, info, executionResults);
                }
            } else {
                buildUpdate(doc, collection, row, parentKeys, level + 1, info, executionResults, operation);
            }
        }
    }

    class UpdateOperationImpl implements UpdateOperation {
        public void execute(MongoDocument doc, DBCollection collection, DBObject row, DBObject dataRow,
                RowInfo rowInfo, List<WriteResult> executionResults) throws TranslatorException {
            if (dataRow instanceof BasicDBList) {
                BasicDBList updatedDoc = new BasicDBList();
                boolean update = visitor.updateMerge((BasicDBList) dataRow, rowInfo, updatedDoc);
                if (update) {
                    BasicDBObject m = new BasicDBObject("_id", row.get("_id"));//$NON-NLS-1$ //$NON-NLS-2$
                    BasicDBObject u = new BasicDBObject(rowInfo.getId(doc), updatedDoc);
                    LogManager.logDetail(LogConstants.CTX_CONNECTOR, "update - {\"$match\": {" + m + "}}"); //$NON-NLS-1$ //$NON-NLS-2$
                    LogManager.logDetail(LogConstants.CTX_CONNECTOR, "update - {\"$set\": {" + u + "}}"); //$NON-NLS-1$ //$NON-NLS-2$
                    WriteResult result = collection.update(m, new BasicDBObject("$set", u), false, true, //$NON-NLS-1$
                            WriteConcern.ACKNOWLEDGED);
                    executionResults.add(result);
                }
            } else {
                BasicDBObject m = new BasicDBObject("_id", row.get("_id"));//$NON-NLS-1$ //$NON-NLS-2$
                boolean update = visitor.updateMerge((BasicDBObject) dataRow, rowInfo);
                if (update) {
                    BasicDBObject u = new BasicDBObject(rowInfo.getId(doc), dataRow);
                    LogManager.logDetail(LogConstants.CTX_CONNECTOR, "update - {\"$match\": {" + m + "}}"); //$NON-NLS-1$ //$NON-NLS-2$
                    LogManager.logDetail(LogConstants.CTX_CONNECTOR, "update - {\"$set\": {" + u + "}}"); //$NON-NLS-1$ //$NON-NLS-2$
                    WriteResult result = collection.update(m, new BasicDBObject("$set", u), false, true, //$NON-NLS-1$
                            WriteConcern.ACKNOWLEDGED);
                    executionResults.add(result);
                }
            }
        }
    }

    class DeleteOperationImpl implements UpdateOperation {
        private DBObject queryMatch;

        public DeleteOperationImpl(DBObject match) {
            this.queryMatch = match;
        }

        public void execute(MongoDocument doc, DBCollection collection, DBObject row, DBObject dataRow,
                RowInfo rowInfo, List<WriteResult> executionResults) throws TranslatorException {

            if (dataRow instanceof BasicDBList) {
                BasicDBObject pull = (BasicDBObject) visitor.getPullQuery().get(rowInfo.mergedTableName);
                if (this.queryMatch.keySet().isEmpty()) {
                    queryMatch = QueryBuilder.start(rowInfo.getId(doc)).exists(true).get();
                    pull = new BasicDBObject(rowInfo.getId(doc), pull != null ? pull : new BasicDBObject());
                    LogManager.logInfo(LogConstants.CTX_CONNECTOR,
                            "update - {\"$match\": {" + this.queryMatch + "}}"); //$NON-NLS-1$ //$NON-NLS-2$
                    LogManager.logInfo(LogConstants.CTX_CONNECTOR, "update - {\"$pull\": {" + pull + "}}"); //$NON-NLS-1$ //$NON-NLS-2$                    
                    WriteResult result = collection.update(this.queryMatch, new BasicDBObject("$pull", pull), false, //$NON-NLS-1$
                            true, WriteConcern.ACKNOWLEDGED);
                    executionResults.add(result);
                } else {
                    BasicDBList updatedDoc = new BasicDBList();
                    boolean update = visitor.updateDelete((BasicDBList) dataRow, rowInfo, updatedDoc);
                    if (update) {
                        BasicDBObject m = new BasicDBObject("_id", row.get("_id"));//$NON-NLS-1$ //$NON-NLS-2$
                        BasicDBObject u = new BasicDBObject(rowInfo.getId(doc), updatedDoc);
                        LogManager.logDetail(LogConstants.CTX_CONNECTOR, "update - {\"$match\": {" + m + "}}"); //$NON-NLS-1$ //$NON-NLS-2$
                        LogManager.logDetail(LogConstants.CTX_CONNECTOR, "update - {\"$set\": {" + u + "}}"); //$NON-NLS-1$ //$NON-NLS-2$
                        WriteResult result = collection.update(m, new BasicDBObject("$set", u), false, true, //$NON-NLS-1$
                                WriteConcern.ACKNOWLEDGED);
                        executionResults.add(result);
                    }
                }
            } else {
                if (this.queryMatch.keySet().isEmpty()) {
                    queryMatch = QueryBuilder.start(rowInfo.getId(doc)).exists(true).get();
                }
                BasicDBObject u = new BasicDBObject(rowInfo.getId(doc), ""); //$NON-NLS-1$
                LogManager.logInfo(LogConstants.CTX_CONNECTOR, "update - {\"$match\": {" + this.queryMatch + "}}"); //$NON-NLS-1$ //$NON-NLS-2$
                LogManager.logInfo(LogConstants.CTX_CONNECTOR, "update - {\"$unset\": {" + u + "}}"); //$NON-NLS-1$ //$NON-NLS-2$
                WriteResult result = collection.update(this.queryMatch, new BasicDBObject("$unset", u), false, true, //$NON-NLS-1$
                        WriteConcern.ACKNOWLEDGED);
                executionResults.add(result);
            }
        }
    }

    private BasicDBObject buildProjectForUpdate(MongoDocument doc) throws TranslatorException {
        BasicDBObject project = new BasicDBObject();
        // the preview document for update
        String aliasDocumentName = doc.getQualifiedName(false).replace('.', '_');
        project.append(aliasDocumentName, "$" + doc.getQualifiedName(false)); //$NON-NLS-1$

        while (doc.isMerged()) {
            doc = doc.getMergeDocument();
            if (doc.isMerged()) {
                project.append(doc.getQualifiedName(false), "$" + doc.getQualifiedName(false) + "._id"); //$NON-NLS-1$ //$NON-NLS-2$
            }
        }
        project.append("_id", "$_id"); //$NON-NLS-1$ //$NON-NLS-2$
        return project;
    }

    private List<String> parentKeyNames(MongoDocument doc) throws TranslatorException {
        ArrayList<String> list = new ArrayList<String>();
        while (doc.isMerged()) {
            doc = doc.getMergeDocument();
            list.add(0, doc.getQualifiedName(false));
        }
        return list;
    }

    private void removeParentKey(MongoDocument document, BasicDBObject row) throws TranslatorException {
        Table source = document.getTable();
        Table target = document.getMergeTable();

        for (ForeignKey fk : source.getForeignKeys()) {
            if (fk.getReferenceTableName().equals(target.getName())) {
                for (int i = 0; i < fk.getColumns().size(); i++) {
                    if (row != null) {
                        row.remove(fk.getColumns().get(i).getName());
                    }
                }
            }
        }
    }

    private void updateReferenceTables(DBCollection collection, MongoDocument mongoDoc, DBObject match,
            AggregationOptions options) throws TranslatorException {
        DBObject m = new BasicDBObject("$match", match); //$NON-NLS-1$
        Cursor output = collection.aggregate(Arrays.asList(m), options);
        while (output.hasNext()) {
            DBObject row = output.next();
            if (row != null) {
                for (MergeDetails ref : mongoDoc.getEmbeddedIntoReferences()) {
                    DBCollection parent = getCollection(ref.getParentTable());
                    //DBObject parentmatch = new BasicDBObject(ref.getReferenceName()+".$id", row.get("_id")); //$NON-NLS-1$ //$NON-NLS-2$
                    DBObject parentmatch = buildParentMatch(row, ref);
                    row.removeField("_id"); //$NON-NLS-1$
                    parent.update(parentmatch, new BasicDBObject("$set", new BasicDBObject(ref.getName(), row)), //$NON-NLS-1$
                            false, true, WriteConcern.ACKNOWLEDGED);

                    // see if there are nested references
                    Table parentTable = this.metadata.getTable(mongoDoc.getTable().getParent().getName(),
                            ref.getParentTable());
                    MongoDocument parentMongoDocument = new MongoDocument(parentTable, this.metadata);
                    if (parentMongoDocument.isEmbeddable()) {
                        updateReferenceTables(parent, parentMongoDocument, parentmatch, options);
                    }
                }
            }
        }
    }

    private DBObject buildParentMatch(DBObject row, MergeDetails ref) {
        DBObject parentmatch = new BasicDBObject();
        Object rowid = row.get("_id"); //$NON-NLS-1$
        if (rowid instanceof BasicDBObject) {
            // composite key..
            for (int i = 0; i < ref.getColumns().size(); i++) {
                parentmatch.put(ref.getColumns().get(i),
                        ((BasicDBObject) rowid).get(ref.getReferenceColumns().get(i)));
            }
        } else {
            parentmatch.put(ref.getColumns().get(0), rowid);
        }
        return parentmatch;
    }

    private LinkedHashMap<String, DBObject> fetchEmbeddedDocuments() {
        LinkedHashMap<String, DBObject> additionalDocuments = new LinkedHashMap<String, DBObject>();

        // check if there are any other documents that can be embedded in this
        // document
        MongoDocument mongoDoc = this.visitor.mongoDoc;
        if (mongoDoc.hasEmbeddedDocuments()) {
            for (String docName : mongoDoc.getEmbeddedDocumentNames()) {
                DBObject document = mongoDoc.getEmbeddedDocument(this.mongoDB, docName);
                if (document == null) {
                    continue;
                }
                additionalDocuments.put(docName, document);
            }
        }
        return additionalDocuments;
    }

    private DBCollection getCollection(String name) throws TranslatorException {
        return getCollection(this.metadata.getTable(this.visitor.mongoDoc.getTable().getParent().getName(), name));
    }

    private DBCollection getCollection(Table table) {
        DBCollection collection;
        if (!this.mongoDB.collectionExists(table.getName())) {
            collection = this.mongoDB.createCollection(table.getName(), null);

            // since this is the first time creating the tables; create the indexes on the collection
            // index on foreign keys
            for (ForeignKey record : table.getForeignKeys()) {
                createIndex(collection, record, false);
            }

            // index on unique
            for (KeyRecord record : table.getUniqueKeys()) {
                createIndex(collection, record, true);
            }

            // index on index keys
            for (KeyRecord record : table.getIndexes()) {
                createIndex(collection, record, false);
            }
        } else {
            collection = this.mongoDB.getCollection(table.getName());
        }
        return collection;
    }

    private void createIndex(DBCollection collection, KeyRecord record, boolean unique) {
        BasicDBObject key = new BasicDBObject();
        for (Column c : record.getColumns()) {
            key.append(getRecordName(c), 1);
        }
        BasicDBObject options = new BasicDBObject();
        options.put("name", record.getName()); //$NON-NLS-1$
        options.put("ns", collection.getName()); //$NON-NLS-1$
        if (unique) {
            options.put("unique", Boolean.TRUE); //$NON-NLS-1$
        }
        collection.createIndex(key, options);
    }

    @Override
    public int[] getUpdateCounts() throws DataNotAvailableException, TranslatorException {
        return this.results;
    }

    private void addAutoGeneretedKeys(WriteResult result) throws TranslatorException {
        Table table = this.visitor.mongoDoc.getTargetTable();

        int cols = table.getPrimaryKey().getColumns().size();
        Class<?>[] columnDataTypes = new Class<?>[cols];
        String[] columnNames = new String[cols];
        //this is typically expected to be an int/long, but we'll be general here.  we may eventual need the type logic off of the metadata importer
        for (int i = 0; i < cols; i++) {
            columnDataTypes[i] = table.getPrimaryKey().getColumns().get(i).getJavaType();
            columnNames[i] = table.getPrimaryKey().getColumns().get(i).getName();
        }
        GeneratedKeys generatedKeys = this.executionContext.getCommandContext().returnGeneratedKeys(columnNames,
                columnDataTypes);
        List<Object> vals = new ArrayList<Object>(columnDataTypes.length);
        for (int i = 0; i < columnDataTypes.length; i++) {
            Object value = this.executionFactory.retrieveValue(result.getField(columnNames[i]), columnDataTypes[i],
                    this.mongoDB, columnNames[i], columnNames[i]);
            vals.add(value);
        }
        generatedKeys.addKey(vals);
    }
}