All Downloads are FREE. Search and download functionalities are using the official Maven repository.

util.OpLog.scala Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (c) 2010 MongoDB, Inc. 
 * Copyright (c) 2009 Novus Partners, Inc. 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * For questions and comments about this product, please see the project page at:
 *
 * http://github.com/mongodb/casbah
 *
 */

package com.mongodb.casbah
package util

import com.mongodb.{ casbah, Bytes }

import com.mongodb.casbah.Imports._
import com.mongodb.casbah.commons.Logging

import org.bson.types.BSONTimestamp

/**
 * For a more detailed understanding of how the MongoDB Oplog works, see Kristina Chodorow's blogpost:
 *
 * http://www.kchodorow.com/blog/2010/10/12/replication-internals/
 */
class MongoOpLog(
    mongoClient:    MongoClient           = MongoClient(),
    startTimestamp: Option[BSONTimestamp] = None,
    namespace:      Option[String]        = None,
    replicaSet:     Boolean               = true
) extends Iterator[MongoOpLogEntry] with Logging {

  implicit object BSONTimestampOk extends ValidDateOrNumericType[org.bson.types.BSONTimestamp]

  protected val local: MongoDB = mongoClient("local")
  protected val oplogName: String = if (replicaSet) "oplog.rs" else "oplog.$main"
  protected val oplog: MongoCollection = local(oplogName)

  val tsp: BSONTimestamp = verifyOpLog

  log.debug("Beginning monitoring OpLog at '%s'", tsp)

  val q = namespace match {
    case Some(ns) => ("ts" $gt tsp) ++ ("ns" -> ns)
    case None     => "ts" $gt tsp
  }

  log.debug("OpLog Filter: '%s'", q)

  // scalastyle:off public.methods.have.type
  val cursor = oplog.find(q)
  cursor.option = Bytes.QUERYOPTION_TAILABLE
  cursor.option = Bytes.QUERYOPTION_AWAITDATA

  def next() = MongoOpLogEntry(cursor.next())

  // scalastyle:on public.methods.have.type

  def hasNext: Boolean = cursor.hasNext

  def close(): Unit = cursor.close()

  def verifyOpLog: BSONTimestamp = {
    // Verify the oplog exists
    val last = oplog.find().sort(MongoDBObject("$natural" -> 1)).limit(1)
    assume(
      last.hasNext,
      "No oplog found. mongod must be a --master or belong to a Replica Set."
    )

    /**
     * If a startTimestamp was specified attempt to sync from there.
     * An exception is thrown if the timestamp isn't found because
     * you won't be able to sync.
     */
    startTimestamp match {
      case Some(ts) => {
        oplog.findOne(MongoDBObject("ts" -> ts)).orElse(
          throw new Exception("No oplog entry for requested start timestamp.")
        )
        ts
      }
      case None => last.next().as[BSONTimestamp]("ts")
    }
  }

}

object MongoOpLogEntry {
  // scalastyle:off public.methods.have.type
  def apply(entry: MongoDBObject) = entry("op") match {
    case InsertOp.typeCode =>
      MongoInsertOperation(
        entry.as[BSONTimestamp]("ts"),
        entry.getAs[Long]("h"),

        /** TODO - It won't be there for Master/Slave, but should we check it for RS? */
        entry.as[String]("ns"),
        entry.as[DBObject]("o")
      )
    case UpdateOp.typeCode =>
      MongoUpdateOperation(
        entry.as[BSONTimestamp]("ts"),
        entry.getAs[Long]("h"),

        /** TODO - It won't be there for Master/Slave, but should we check it for RS? */
        entry.as[String]("ns"),
        entry.as[DBObject]("o"),
        entry.as[DBObject]("o2")
      )
    case DeleteOp.typeCode =>
      MongoDeleteOperation(
        entry.as[BSONTimestamp]("ts"),
        entry.getAs[Long]("h"),

        /** TODO - It won't be there for Master/Slave, but should we check it for RS? */
        entry.as[String]("ns"),
        entry.as[DBObject]("o")
      )
  }
}

sealed trait MongoOpType {
  def typeCode: String
}

case object InsertOp extends MongoOpType {
  val typeCode = "i"
}

case object UpdateOp extends MongoOpType {
  val typeCode = "u"
}

case object DeleteOp extends MongoOpType {
  val typeCode = "d"
}

sealed trait MongoOpLogEntry {
  val timestamp: BSONTimestamp
  lazy val ts = timestamp

  /** Master/Slave does *not* include the opID, so make it Option. */
  val opID: Option[Long]
  lazy val h = opID

  val opType: MongoOpType
  lazy val op = opType

  val namespace: String
  lazy val ns = namespace

  val document: MongoDBObject
  lazy val o = document

}

case class MongoInsertOperation(timestamp: BSONTimestamp, opID: Option[Long], namespace: String, document: MongoDBObject) extends MongoOpLogEntry {
  val opType = InsertOp
}

case class MongoUpdateOperation(timestamp: BSONTimestamp, opID: Option[Long], namespace: String,
                                document: MongoDBObject, documentID: MongoDBObject) extends MongoOpLogEntry {
  val opType = UpdateOp
  /** In updates, 'o' gives the modifications, and 'o2' includes the 'update criteria' (the query to run) */
  lazy val o2 = documentID
}

case class MongoDeleteOperation(timestamp: BSONTimestamp, opID: Option[Long], namespace: String, document: MongoDBObject) extends MongoOpLogEntry {
  val opType = DeleteOp
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy