Friday 4 October 2013

Java: writing a MongoDB driver

I didn't set out to write a MongoDB driver; it came about while I was trying to write proof-of-concept code for my Java binary protocol API. The MongoDB wire protocol just happened to be well documented and easy to implement.

Caveats:

  • No binaries have been published; sources are in a git repository.
  • The code presented here is not a substitute for the official driver.
  • Information pertains to MongoDB 2.2.4.
  • APIs may change without warning.

This post is informational; not a tutorial. Everything presented here is pre-alpha code.

MongoDB

The constituent parts of a MongoDB driver:

From a programmer's point of view a MongoDB instance is mainly a storage mechanism for arbitrary data-graphs with the usual object mapping constraints.

The MongoDB documentation is pretty good - I wrote code based on the API documentation and it mostly worked first time. I was able to perform basic CRUD operations over the network without invoking any provided code.

Archeobyte MongoDB

The driver is based on Archeobyte for binary protocol mapping. Here is an example BSON regular expression type implementation that uses annotations:

import net.sf.archeobyte.DataType;
import net.sf.archeobyte.DataTypeFactory;
import net.sf.archeobyte.Index;
import net.sf.archeobyte.bson.convert.CStringConverter;
import net.sf.archeobyte.bson.data.Assertion;

public final class BsonRegex {
  @Index(0)
  @DataType(CStringConverter.class)
  public final String pattern;
  @Index(1)
  @DataType(CStringConverter.class)
  public final String options;

  @DataTypeFactory(mirror = true)
  public BsonRegex(String pattern, String options) {
    this.pattern = Assertion.notNull(pattern);
    this.options = Assertion.notNull(options);
  }

 

There was a couple of days work in creating the mapping types and converters. Here are some CRUD operations using the resultant code:

import java.io.IOException;
import java.util.*;
import net.sf.archeobyte.bson.types.BsonArray;
import net.sf.archeobyte.bson.types.BsonDatetime;
import net.sf.archeobyte.bson.types.BsonDocument;
import net.sf.archeobyte.bson.types.BsonTimestamp;
import net.sf.archeobyte.mongowire.Wire;
import net.sf.archeobyte.mongowire.type.OpDelete;
import net.sf.archeobyte.mongowire.type.OpDeleteFlag;
import net.sf.archeobyte.mongowire.type.OpInsert;
import net.sf.archeobyte.mongowire.type.OpInsertFlag;
import net.sf.archeobyte.mongowire.type.OpQuery;
import net.sf.archeobyte.mongowire.type.OpQueryFlag;
import net.sf.archeobyte.mongowire.type.OpReply;

public class IntegrationTest {
  private static OpReply printAll(Wire wire, String collectionName) throws IOException {
    Set<OpQueryFlag> noflags = Collections.emptySet();
    BsonDocument empty = BsonDocument.documentBuilder().build();
    OpQuery query = new OpQuery(noflags, collectionName, 0, 0, empty, empty);
    OpReply reply = wire.send(query);
    System.out.println(reply.documents);
    return reply;
  }

  private static void deleteAll(Wire wire, String collectionName) throws IOException {
    Set<OpDeleteFlag> noFlags = Collections.emptySet();
    BsonDocument empty = BsonDocument.documentBuilder().build();
    OpDelete delete = new OpDelete(collectionName, noFlags, empty);
    wire.send(delete);
  }

  private static void insertData(Wire wire, String collectionName) throws IOException {
    BsonArray array = BsonArray.arrayBuilder().and("string")
                               .and(new BsonTimestamp(System.currentTimeMillis()))
                               .and(new BsonDatetime(System.currentTimeMillis())).and(123L).build();
    BsonDocument arrayDoc = BsonDocument.documentBuilder().with("array", array).build();
    BsonDocument stringDoc = BsonDocument.documentBuilder().with("foo", "bar").build();
    List<BsonDocument> docs = Arrays.asList(arrayDoc, stringDoc);
    OpInsert insert = new OpInsert(Collections.<OpInsertFlag> emptySet(), collectionName, docs);
    wire.send(insert);
  }

  private static void printError(Wire wire, String db) throws IOException {
    Set<OpQueryFlag> noflags = Collections.emptySet();
    BsonDocument lastError = BsonDocument.documentBuilder().with("getLastError", 1).build();
    BsonDocument empty = BsonDocument.documentBuilder().build();
    OpQuery query = new OpQuery(noflags, db + ".$cmd", 0, 0, lastError, empty);
    OpReply reply = wire.send(query);
    System.out.println("Errors: " + reply.documents);
  }

  public static void main(String[] args) throws IOException {
    Wire wire = new Wire("localhost");
    try {
      String db = "test";
      String collectionName = db + ".testData";
      insertData(wire, collectionName);
      printError(wire, db);
      printAll(wire, collectionName);
      deleteAll(wire, collectionName);
      printError(wire, db);
      printAll(wire, collectionName);
    } finally {
      wire.close();
    }
  }
}

 

No comments:

Post a Comment

All comments are moderated