/* * 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, true, WriteConcern.ACKNOWLEDGED)); //$NON-NLS-1$ } 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, WriteConcern.ACKNOWLEDGED)); //$NON-NLS-1$ } } 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, WriteConcern.ACKNOWLEDGED)); //$NON-NLS-1$ } // 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, WriteConcern.ACKNOWLEDGED); //$NON-NLS-1$ 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, WriteConcern.ACKNOWLEDGED); //$NON-NLS-1$ 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, true, //$NON-NLS-1$ 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, WriteConcern.ACKNOWLEDGED); //$NON-NLS-1$ 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, WriteConcern.ACKNOWLEDGED); //$NON-NLS-1$ 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$ 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); } }