1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
|
# Copyright (C) 2009-2013 MongoDB, 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.
require 'digest/md5'
require 'socket'
module BSON
def BSON::ObjectId(s)
ObjectId.from_string(s)
end
# Generates MongoDB object ids.
class ObjectId
attr_accessor :data
# Create a new object id. If no parameter is given, an id corresponding
# to the ObjectId BSON data type will be created. This is a 12-byte value
# consisting of a 4-byte timestamp, a 3-byte machine id, a 2-byte process id,
# and a 3-byte counter.
#
# @param [Array] data should be an array of bytes. If you want
# to generate a standard MongoDB object id, leave this argument blank.
#
# @option opts :data (nil) An array of bytes to use as the object id.
# @option opts :time (nil) The value of this object ids timestamp. Note that
# the remaining bytes will consist of the standard machine id, pid, and counter. If
# you need a zeroed timestamp, used ObjectId.from_time.
def initialize(data=nil, time=nil)
if data && (!data.is_a?(Array) || data.size != 12)
raise InvalidObjectId, 'ObjectId requires 12 byte array'
end
@data = data || generate(time)
end
# Determine if the supplied string is legal. Legal strings will
# consist of 24 hexadecimal characters.
#
# @param [String] str
#
# @return [Boolean]
def self.legal?(str)
str =~ /\A[0-9a-f]{24}\z/i ? true : false
end
# Create an object id from the given time. This is useful for doing range
# queries; it works because MongoDB's object ids begin
# with a timestamp.
#
# @param [Time] time a utc time to encode as an object id.
#
# @option opts [:unique] (false) If false, the object id's bytes
# succeeding the timestamp will be zeroed; if true, they'll
# consist of the standard machine id, pid, and counter.
#
# @return [BSON::ObjectId]
#
# @example Return all document created before Jan 1, 2010.
# time = Time.utc(2010, 1, 1)
# time_id = ObjectId.from_time(time)
# collection.find({'_id' => {'$lt' => time_id}})
def self.from_time(time, opts={})
unique = opts.fetch(:unique, false)
if unique
self.new(nil, time)
else
self.new([time.to_i,0,0].pack("NNN").unpack("C12"))
end
end
# Adds a primary key to the given document if needed.
#
# @param [Hash] doc a document requiring an _id.
#
# @return [BSON::ObjectId, Object] returns a newly-created or
# current _id for the given document.
def self.create_pk(doc)
doc.has_key?(:_id) || doc.has_key?('_id') ? doc : doc.merge!(:_id => self.new)
end
# Check equality of this object id with another.
#
# @param [BSON::ObjectId] object_id
def eql?(object_id)
object_id.kind_of?(BSON::ObjectId) and self.data == object_id.data
end
alias_method :==, :eql?
# Get a unique hashcode for this object.
# This is required since we've defined an #eql? method.
#
# @return [Integer]
def hash
@data.hash
end
# Get an array representation of the object id.
#
# @return [Array]
def to_a
@data.dup
end
# Given a string representation of an ObjectId, return a new ObjectId
# with that value.
#
# @param [String] str
#
# @return [BSON::ObjectId]
def self.from_string(str)
raise InvalidObjectId, "illegal ObjectId format: #{str}" unless legal?(str)
data = []
12.times do |i|
data[i] = str[i * 2, 2].to_i(16)
end
self.new(data)
end
# Get a string representation of this object id.
#
# @return [String]
def to_s
@data.map {|e| v=e.to_s(16); v.size == 1 ? "0#{v}" : v }.join
end
def inspect
"BSON::ObjectId('#{to_s}')"
end
# Convert to MongoDB extended JSON format. Since JSON includes type information,
# but lacks an ObjectId type, this JSON format encodes the type using an $oid key.
#
# @return [String] the object id represented as MongoDB extended JSON.
def to_json(*a)
"{\"$oid\": \"#{to_s}\"}"
end
# Create the JSON hash structure convert to MongoDB extended format. Rails 2.3.3
# introduced as_json to create the needed hash structure to encode objects into JSON.
#
# @return [Hash] the hash representation as MongoDB extended JSON
def as_json(options ={})
{"$oid" => to_s}
end
# Return the UTC time at which this ObjectId was generated. This may
# be used in lieu of a created_at timestamp since this information
# is always encoded in the object id.
#
# @return [Time] the time at which this object was created.
def generation_time
Time.at(@data.pack("C4").unpack("N")[0]).utc
end
def self.machine_id
@@machine_id
end
private
if RUBY_PLATFORM =~ /java/ && BSON.extension?
@@generator = Java::OrgBsonTypes::ObjectId
@@machine_id = [@@generator.genMachineId].pack("N")[0,3]
def generate(oid_time=nil)
data = (oid_time ? @@generator.new(oid_time) : @@generator.new)
oid = ''
oid += [data.timeSecond].pack("N")
oid += [data._machine].pack("N")
oid += [data._inc].pack("N")
oid.unpack("C12")
end
else
@@lock = Mutex.new
@@index = 0
@@machine_id = Digest::MD5.digest(Socket.gethostname)[0, 3]
# We need to check whether BSON_CODER is defined because it's required by
# the BSON C extensions.
if defined?(BSON::BSON_CODER) && BSON::BSON_CODER == BSON::BSON_RUBY
# This gets overwritten by the C extension if it loads.
def generate(oid_time=nil)
oid = ''
# 4 bytes current time
if oid_time
t = oid_time.to_i
else
t = Time.new.to_i
end
oid += [t].pack("N")
# 3 bytes machine
oid += @@machine_id
# 2 bytes pid
oid += [Process.pid % 0xFFFF].pack("n")
# 3 bytes inc
oid += [get_inc].pack("N")[1, 3]
oid.unpack("C12")
end
def get_inc
@@lock.synchronize do
@@index = (@@index + 1) % 0xFFFFFF
end
end
end
end
end
end
|