PageRenderTime 28ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 0ms

/casbah-core/src/main/scala/util/OpLog.scala

http://github.com/mongodb/casbah
Scala | 199 lines | 120 code | 38 blank | 41 comment | 2 complexity | 6c9b3431fa0c215c9dcb41482f5e37cf MD5 | raw file
Possible License(s): Apache-2.0
  1. /**
  2. * Copyright (c) 2010 MongoDB, Inc. <http://mongodb.com>
  3. * Copyright (c) 2009 Novus Partners, Inc. <http://novus.com>
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. * For questions and comments about this product, please see the project page at:
  18. *
  19. * http://github.com/mongodb/casbah
  20. *
  21. */
  22. package com.mongodb.casbah
  23. package util
  24. import com.mongodb.{ casbah, Bytes }
  25. import com.mongodb.casbah.Imports._
  26. import com.mongodb.casbah.commons.Logging
  27. import org.bson.types.BSONTimestamp
  28. /**
  29. * For a more detailed understanding of how the MongoDB Oplog works, see Kristina Chodorow's blogpost:
  30. *
  31. * http://www.kchodorow.com/blog/2010/10/12/replication-internals/
  32. */
  33. class MongoOpLog(
  34. mongoClient: MongoClient = MongoClient(),
  35. startTimestamp: Option[BSONTimestamp] = None,
  36. namespace: Option[String] = None,
  37. replicaSet: Boolean = true
  38. ) extends Iterator[MongoOpLogEntry] with Logging {
  39. implicit object BSONTimestampOk extends ValidDateOrNumericType[org.bson.types.BSONTimestamp]
  40. protected val local: MongoDB = mongoClient("local")
  41. protected val oplogName: String = if (replicaSet) "oplog.rs" else "oplog.$main"
  42. protected val oplog: MongoCollection = local(oplogName)
  43. val tsp: BSONTimestamp = verifyOpLog
  44. log.debug("Beginning monitoring OpLog at '%s'", tsp)
  45. val q = namespace match {
  46. case Some(ns) => ("ts" $gt tsp) ++ ("ns" -> ns)
  47. case None => "ts" $gt tsp
  48. }
  49. log.debug("OpLog Filter: '%s'", q)
  50. // scalastyle:off public.methods.have.type
  51. val cursor = oplog.find(q)
  52. cursor.option = Bytes.QUERYOPTION_TAILABLE
  53. cursor.option = Bytes.QUERYOPTION_AWAITDATA
  54. def next() = MongoOpLogEntry(cursor.next())
  55. // scalastyle:on public.methods.have.type
  56. def hasNext: Boolean = cursor.hasNext
  57. def close(): Unit = cursor.close()
  58. def verifyOpLog: BSONTimestamp = {
  59. // Verify the oplog exists
  60. val last = oplog.find().sort(MongoDBObject("$natural" -> 1)).limit(1)
  61. assume(
  62. last.hasNext,
  63. "No oplog found. mongod must be a --master or belong to a Replica Set."
  64. )
  65. /**
  66. * If a startTimestamp was specified attempt to sync from there.
  67. * An exception is thrown if the timestamp isn't found because
  68. * you won't be able to sync.
  69. */
  70. startTimestamp match {
  71. case Some(ts) => {
  72. oplog.findOne(MongoDBObject("ts" -> ts)).orElse(
  73. throw new Exception("No oplog entry for requested start timestamp.")
  74. )
  75. ts
  76. }
  77. case None => last.next().as[BSONTimestamp]("ts")
  78. }
  79. }
  80. }
  81. object MongoOpLogEntry {
  82. // scalastyle:off public.methods.have.type
  83. def apply(entry: MongoDBObject) = entry("op") match {
  84. case InsertOp.typeCode =>
  85. MongoInsertOperation(
  86. entry.as[BSONTimestamp]("ts"),
  87. entry.getAs[Long]("h"),
  88. /** TODO - It won't be there for Master/Slave, but should we check it for RS? */
  89. entry.as[String]("ns"),
  90. entry.as[DBObject]("o")
  91. )
  92. case UpdateOp.typeCode =>
  93. MongoUpdateOperation(
  94. entry.as[BSONTimestamp]("ts"),
  95. entry.getAs[Long]("h"),
  96. /** TODO - It won't be there for Master/Slave, but should we check it for RS? */
  97. entry.as[String]("ns"),
  98. entry.as[DBObject]("o"),
  99. entry.as[DBObject]("o2")
  100. )
  101. case DeleteOp.typeCode =>
  102. MongoDeleteOperation(
  103. entry.as[BSONTimestamp]("ts"),
  104. entry.getAs[Long]("h"),
  105. /** TODO - It won't be there for Master/Slave, but should we check it for RS? */
  106. entry.as[String]("ns"),
  107. entry.as[DBObject]("o")
  108. )
  109. case NoOp.typeCode =>
  110. MongoNoOperation(
  111. entry.as[BSONTimestamp]("ts"),
  112. entry.getAs[Long]("h"),
  113. /** TODO - It won't be there for Master/Slave, but should we check it for RS? */
  114. entry.as[String]("ns"),
  115. entry.as[DBObject]("o")
  116. )
  117. }
  118. }
  119. sealed trait MongoOpType {
  120. def typeCode: String
  121. }
  122. case object InsertOp extends MongoOpType {
  123. val typeCode = "i"
  124. }
  125. case object UpdateOp extends MongoOpType {
  126. val typeCode = "u"
  127. }
  128. case object DeleteOp extends MongoOpType {
  129. val typeCode = "d"
  130. }
  131. case object NoOp extends MongoOpType {
  132. val typeCode = "n"
  133. }
  134. sealed trait MongoOpLogEntry {
  135. val timestamp: BSONTimestamp
  136. lazy val ts = timestamp
  137. /** Master/Slave does *not* include the opID, so make it Option. */
  138. val opID: Option[Long]
  139. lazy val h = opID
  140. val opType: MongoOpType
  141. lazy val op = opType
  142. val namespace: String
  143. lazy val ns = namespace
  144. val document: MongoDBObject
  145. lazy val o = document
  146. }
  147. case class MongoInsertOperation(timestamp: BSONTimestamp, opID: Option[Long], namespace: String, document: MongoDBObject) extends MongoOpLogEntry {
  148. val opType = InsertOp
  149. }
  150. case class MongoUpdateOperation(timestamp: BSONTimestamp, opID: Option[Long], namespace: String,
  151. document: MongoDBObject, documentID: MongoDBObject) extends MongoOpLogEntry {
  152. val opType = UpdateOp
  153. /** In updates, 'o' gives the modifications, and 'o2' includes the 'update criteria' (the query to run) */
  154. lazy val o2 = documentID
  155. }
  156. case class MongoDeleteOperation(timestamp: BSONTimestamp, opID: Option[Long], namespace: String, document: MongoDBObject) extends MongoOpLogEntry {
  157. val opType = DeleteOp
  158. }
  159. case class MongoNoOperation(timestamp: BSONTimestamp, opID: Option[Long], namespace: String, document: MongoDBObject) extends MongoOpLogEntry {
  160. val opType = NoOp
  161. }