@Override
  public BasicDocumentRevision createLocalDocument(String docId, final DocumentBody body) {
    Preconditions.checkState(this.isOpen(), "Database is closed");
    CouchUtils.validateDocumentId(docId);
    Preconditions.checkNotNull(body, "Input document body can not be null");
    this.sqlDb.beginTransaction();
    try {
      String firstRevId = CouchUtils.getFirstLocalDocRevisionId();
      ContentValues values = new ContentValues();
      values.put("docid", docId);
      values.put("revid", firstRevId);
      values.put("json", body.asBytes());

      long lineId = this.sqlDb.insert("localdocs", values);
      if (lineId < 0) {
        throw new IllegalArgumentException(
            "Can not insert new local doc, likely the docId exists already: " + docId);
      } else {
        Log.d(LOG_TAG, "New local doc inserted: " + lineId + ", " + docId);
      }

      this.sqlDb.setTransactionSuccessful();
      return getLocalDocument(docId, firstRevId);
    } finally {
      this.sqlDb.endTransaction();
    }
  }
  @Override
  public BasicDocumentRevision createDocument(String docId, final DocumentBody body) {
    Preconditions.checkState(this.isOpen(), "Database is closed");
    CouchUtils.validateDocumentId(docId);
    Preconditions.checkNotNull(body, "Input document body can not be null");
    this.validateDBBody(body);

    DocumentCreated documentCreated = null;
    this.sqlDb.beginTransaction();
    try {
      long docNumericID = insertDocumentID(docId);
      if (docNumericID < 0) {
        throw new IllegalArgumentException(
            "Can not insert new doc, likely the docId exists already: " + docId);
      }

      String revisionId = CouchUtils.getFirstRevisionId();
      long newSequence =
          insertRevision(docNumericID, revisionId, -1l, false, true, body.asBytes(), true);
      if (newSequence < 0) {
        throw new IllegalStateException("Error inserting data, please checking data.");
      }

      BasicDocumentRevision doc = getDocument(docId, revisionId);
      documentCreated = new DocumentCreated(doc);

      Log.d(LOG_TAG, "New document created: " + doc.toString());

      this.sqlDb.setTransactionSuccessful();
      return doc;
    } finally {
      this.sqlDb.endTransaction();
      if (documentCreated != null) {
        eventBus.post(documentCreated);
      }
    }
  }
  @Override
  public void forceInsert(
      DocumentRevision rev, List<String> revisionHistory, Map<String, Object> attachments) {
    Preconditions.checkState(this.isOpen(), "Database is closed");
    Preconditions.checkNotNull(rev, "Input document revision can not be null");
    Preconditions.checkNotNull(revisionHistory, "Input revision history must not be null");
    Preconditions.checkArgument(
        revisionHistory.size() > 0, "Input revision history must not be empty");
    Preconditions.checkArgument(
        checkCurrentRevisionIsInRevisionHistory(rev, revisionHistory),
        "Current revision must exist in revision history.");
    Preconditions.checkArgument(
        checkRevisionIsInCorrectOrder(revisionHistory), "Revision history must be in right order.");
    CouchUtils.validateDocumentId(rev.getId());
    CouchUtils.validateRevisionId(rev.getRevision());

    Log.v(
        LOG_TAG,
        "forceInsert(): " + rev.toString() + ",\n" + JSONUtils.toPrettyJson(revisionHistory));

    DocumentCreated documentCreated = null;
    DocumentUpdated documentUpdated = null;

    boolean ok = true;

    this.sqlDb.beginTransaction();
    try {
      long seq = 0;
      // sequence here is -1, but we need it to insert the attachment - also might be wanted by
      // subscribers
      if (this.containsDocument(rev.getId())) {
        seq = doForceInsertExistingDocumentWithHistory(rev, revisionHistory);
        rev.initialiseSequence(seq);
        // TODO fetch the parent doc?
        documentUpdated = new DocumentUpdated(null, rev);
      } else {
        seq = doForceInsertNewDocumentWithHistory(rev, revisionHistory);
        rev.initialiseSequence(seq);
        documentCreated = new DocumentCreated(rev);
      }
      // now deal with any attachments
      if (attachments != null) {
        for (String att : attachments.keySet()) {
          String data = (String) ((Map<String, Object>) attachments.get(att)).get("data");
          InputStream is = new Base64InputStream(new ByteArrayInputStream(data.getBytes()));
          String type = (String) ((Map<String, Object>) attachments.get(att)).get("content_type");
          // inline attachments are automatically decompressed, so we don't have to worry about that
          UnsavedStreamAttachment ufa = new UnsavedStreamAttachment(is, att, type);
          boolean result = false;
          try {
            PreparedAttachment preparedAttachment =
                new PreparedAttachment(ufa, this.attachmentManager.attachmentsDir);
            result = this.attachmentManager.addAttachment(preparedAttachment, rev);
          } catch (IOException e) {
            Log.e(LOG_TAG, "IOException when preparing attachment " + ufa + ": " + e);
          }
          if (!result) {
            Log.e(
                LOG_TAG,
                "There was a problem adding the attachment "
                    + ufa
                    + " to the datastore; not force inserting this document: "
                    + rev);
            ok = false;
            break;
          }
        }
      }
      if (ok) {
        this.sqlDb.setTransactionSuccessful();
      }
    } finally {
      this.sqlDb.endTransaction();
      if (ok) {
        if (documentCreated != null) {
          eventBus.post(documentCreated);
        } else if (documentUpdated != null) {
          eventBus.post(documentUpdated);
        }
      }
    }
  }