File: object_id.rb

package info (click to toggle)
ruby-bson 1.10.0-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie-kfreebsd
  • size: 264 kB
  • sloc: ruby: 1,551; makefile: 5
file content (226 lines) | stat: -rw-r--r-- 6,837 bytes parent folder | download | duplicates (2)
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 =~ /^[0-9a-f]{24}$/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