From aee3ae2c9dec40ce63e9694ad2c8a8b5895d750f Mon Sep 17 00:00:00 2001 From: Jim Menard Date: Wed, 17 Dec 2008 11:43:08 -0500 Subject: [PATCH] More documentation --- README.rdoc | 2 +- lib/mongo/collection.rb | 46 ++++++++++++++--- lib/mongo/cursor.rb | 7 +++ lib/mongo/db.rb | 94 +++++++++++++++++++++++++++++----- lib/mongo/mongo.rb | 7 +++ lib/mongo/objectid.rb | 11 +++- lib/mongo/query.rb | 28 +++++++++- lib/mongo/util/bson.rb | 19 ++++++- lib/mongo/util/byte_buffer.rb | 17 ++++++ lib/mongo/util/ordered_hash.rb | 17 ++++++ 10 files changed, 223 insertions(+), 25 deletions(-) diff --git a/README.rdoc b/README.rdoc index 5fe0522..deeb716 100644 --- a/README.rdoc +++ b/README.rdoc @@ -10,7 +10,7 @@ for any production data yet. = Demo - $ ruby examples/demo.rb + $ ruby examples/simple.rb Mongo must be running, of course. diff --git a/lib/mongo/collection.rb b/lib/mongo/collection.rb index e6c92d4..715aaaa 100644 --- a/lib/mongo/collection.rb +++ b/lib/mongo/collection.rb @@ -1,3 +1,4 @@ +# --- # Copyright (C) 2008 10gen Inc. # # This program is free software: you can redistribute it and/or modify it @@ -11,12 +12,15 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +# +++ require 'mongo/query' module XGen module Mongo module Driver + + # A named collection of records in a database. class Collection attr_reader :db, :name @@ -26,18 +30,23 @@ module XGen @name = name end + # Return records that match a +selector+ hash. See Mongo docs for + # details. + # # Options: - # * :fields - Array of collection field names; only those will be returned (plus _id if defined) - # * :offset - Start at this record when returning records - # * :limit - Maximum number of records to return - # * :sort - Hash of field names as keys and 1/-1 as values; 1 == ascending, -1 == descending - # + # :fields :: Array of collection field names; only those will be returned (plus _id if defined) + # :offset :: Start at this record when returning records + # :limit :: Maximum number of records to return + # :sort :: Either hash of field names as keys and 1/-1 as values; 1 == + # ascending, -1 == descending, or array of field names (all + # assumed to be sorted in ascending order). def find(selector={}, options={}) fields = options.delete(:fields) fields = nil if fields && fields.empty? @db.query(@name, Query.new(selector, fields, options[:offset] || 0, options[:limit] || 0, options[:sort])) end + # Insert +objects+, which are hashes. def insert(*objects) objects = objects.first if objects.size == 1 && objects.first.is_a?(Array) res = @db.insert_into_db(@name, objects) @@ -45,45 +54,66 @@ module XGen end alias_method :<<, :insert + # Remove the records that match +selector+. def remove(selector={}) @db.remove_from_db(@name, selector) end + # Remove all records. def clear remove({}) end + # Update records that match +selector+ by applying +obj+ as an update. + # If no match, inserts (???). def repsert(selector, obj) @db.repsert_in_db(@name, selector, obj) end + # Update records that match +selector+ by applying +obj+ as an update. def replace(selector, obj) @db.replace_in_db(@name, selector, obj) end - def modify(selector, modifierObj) - raise "no object" unless modifierObj + # Update records that match +selector+ by applying +obj+ as an update. + # Both +selector+ and +modifier_obj+ are required. + def modify(selector, modifier_obj) + raise "no object" unless modifier_obj raise "no selector" unless selector - @db.modify_in_db(@name, selector, modifierObj) + @db.modify_in_db(@name, selector, modifier_obj) end + # Create a new index named +index_name+. +fields+ should be an array + # of field names. def create_index(name, *fields) @db.create_index(@name, name, fields) end + # Drop index +name+. def drop_index(name) @db.drop_index(@name, name) end + # Drop all indexes. def drop_indexes # just need to call drop indexes with no args; will drop them all @db.drop_index(@name, '*') end + # Return an array of hashes, one for each index. Each hash contains: + # + # :name :: Index name + # + # :keys :: Hash whose keys are the names of the fields that make up + # the key and values are integers. + # + # :ns :: Namespace; same as this collection's name. def index_information @db.index_information(@name) end + # Return the number of records that match +selector+. If +selector+ is + # +nil+ or an empty hash, returns the count of all records. def count(selector={}) @db.count(@name, selector || {}) end diff --git a/lib/mongo/cursor.rb b/lib/mongo/cursor.rb index 329fb5c..8156c32 100644 --- a/lib/mongo/cursor.rb +++ b/lib/mongo/cursor.rb @@ -1,3 +1,4 @@ +# --- # Copyright (C) 2008 10gen Inc. # # This program is free software: you can redistribute it and/or modify it @@ -11,6 +12,7 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +# +++ require 'mongo/message' require 'mongo/util/byte_buffer' @@ -20,6 +22,7 @@ module XGen module Mongo module Driver + # A cursor over query results. Returned objects are hashes. class Cursor include Enumerable @@ -33,10 +36,12 @@ module XGen read_all end + # Return +true+ if there are more records to retrieve. def more? num_remaining > 0 end + # Return the next object. Raises an error if necessary. def next_object refill_via_get_more if num_remaining == 0 o = @objects.shift @@ -44,12 +49,14 @@ module XGen o end + # Iterate over each object, yielding it to the given block. def each while more? yield next_object() end end + # Close the cursor. def close @db.send_to_db(KillCursorMessage(@cursor_id)) if @cursor_id @objects = [] diff --git a/lib/mongo/db.rb b/lib/mongo/db.rb index 5b8a637..63274b1 100644 --- a/lib/mongo/db.rb +++ b/lib/mongo/db.rb @@ -1,3 +1,4 @@ +# --- # Copyright (C) 2008 10gen Inc. # # This program is free software: you can redistribute it and/or modify it @@ -11,8 +12,10 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +# +++ require 'socket' +require 'mongo/mongo' require 'mongo/collection' require 'mongo/message' require 'mongo/query' @@ -22,42 +25,64 @@ module XGen module Mongo module Driver + # A Mongo database. class DB + SYSTEM_NAMESPACE_COLLECTION = "system.namespaces" SYSTEM_INDEX_COLLECTION = "system.indexes" SYSTEM_COMMAND_COLLECTION = "$cmd" - # Strict mode means that trying to access a collection that does not - # exist will raise an error. Strict mode is off (false) by default. + # Strict mode enforces collection existence checks. When +true+, + # asking for a collection that does not exist or trying to create a + # collection that already exists raises an error. + # + # Strict mode is off (+false+) by default. Its value can be changed at + # any time. attr_writer :strict # Returns the value of the +strict+ flag. def strict?; @strict; end - attr_reader :name, :socket + # The name of the database. + attr_reader :name - def initialize(db_name, host, port) + # The database's socket. For internal use only. + attr_reader :socket + + # db_name :: The database name + # + # host :: The database host name or IP address. Defaults to 'localhost'. + # + # port :: The database port number. Defaults to + # XGen::Mongo::Driver::Mongo::DEFAULT_PORT. + # + def initialize(db_name, host='localhost', port=XGen::Mongo::Driver::Mongo::DEFAULT_PORT) raise "Invalid DB name" if !db_name || (db_name && db_name.length > 0 && db_name.include?(".")) @name, @host, @port = db_name, host, port @socket = TCPSocket.new(@host, @port) @strict = false end + # Returns an array of collection names. Each name is of the form + # "database_name.collection_name". def collection_names names = collections_info.collect { |doc| doc['name'] || '' } names.delete('') names end + # Returns a cursor over query result hashes. Each hash contains a + # 'name' string and optionally an 'options' hash. If +coll_name+ is + # specified, an array of length 1 is returned. def collections_info(coll_name=nil) selector = {} - selector[:name] = "#{@name}.#{coll_name}" if coll_name + selector[:name] = full_coll_name(coll_name) if coll_name query(SYSTEM_NAMESPACE_COLLECTION, Query.new(selector)) end # Create a collection. If +strict+ is false, will return existing or # new collection. If +strict+ is true, will raise an error if - # collection +name+ does not already exist. + # collection +name+ already exists. def create_collection(name, options={}) # First check existence if collection_names.include?(full_coll_name(name)) @@ -85,7 +110,17 @@ module XGen # Return a collection. If +strict+ is false, will return existing or # new collection. If +strict+ is true, will raise an error if - # collection +name+ already exists. + # collection +name+ does not already exists. + # + # Options: + # + # :capped :: Boolean. If not specified, capped is +false+. + # + # :size :: If +capped+ is +true+, specifies the maximum number of + # bytes. If +false+, specifies the initial extent of the + # collection. + # + # :max :: Max number of records in a capped collection. Optional. def collection(name) return Collection.new(self, name) if collection_names.include?(full_coll_name(name)) if strict? @@ -95,11 +130,13 @@ module XGen end end + # Drop collection +name+. Returns +true+ on success or if the + # collection does not exist, +false+ otherwise. def drop_collection(name) - coll = collection(name) - return true if coll == nil - coll.drop_indexes # Mongo requires that we drop indexes manually + return true unless collection_names.include?(full_coll_name(name)) + coll = collection(name) + coll.drop_indexes # Mongo requires that we drop indexes manually ok?(db_command(:drop => name)) end @@ -111,31 +148,44 @@ module XGen ok?(doc) && is_master.kind_of?(Numeric) && is_master.to_i == 1 end + # Close the connection to the database. def close @socket.close end + # Send a MsgMessage to the database. def send_message(msg) send_to_db(MsgMessage.new(msg)) end + # Send a Query to +collection_name+ and return a Cursor over the + # results. def query(collection_name, query) # TODO synchronize send_to_db(QueryMessage.new(@name, collection_name, query)) return Cursor.new(self, collection_name) end + # Remove the records that match +selector+ from +collection_name+. + # Normally called by Collection#remove or Collection#clear. def remove_from_db(collection_name, selector) # TODO synchronize send_to_db(RemoveMessage.new(@name, collection_name, selector)) end + # Update records in +collection_name+ that match +selector+ by + # applying +obj+ as an update. Normally called by Collection#replace. def replace_in_db(collection_name, selector, obj) # TODO synchronize send_to_db(UpdateMessage.new(@name, collection_name, selector, obj, false)) end + + # Alias for #replace_in_db. Normally called by Collection.modify. alias_method :modify_in_db, :replace_in_db + # Update records in +collection_name+ that match +selector+ by + # applying +obj+ as an update. If no match, inserts (???). Normally + # called by Collection#repsert. def repsert_in_db(collection_name, selector, obj) # TODO if PKInjector, inject # TODO synchronize @@ -143,15 +193,20 @@ module XGen obj end - def count(collection_name, selector) + # Return the number of records in +collection_name+ that match + # +selector+. If +selector+ is +nil+ or an empty hash, returns the + # count of all records. Normally called by Collection#count. + def count(collection_name, selector={}) oh = OrderedHash.new oh[:count] = collection_name - oh[:query] = selector + oh[:query] = selector || {} doc = db_command(oh) return doc['n'].to_i if ok?(doc) raise "Error with count command: #{doc.inspect}" end + # Drop index +name+ from +collection_name+. Normally called from + # Collection#drop_index or Collection#drop_indexes. def drop_index(collection_name, name) oh = OrderedHash.new oh[:deleteIndexes] = collection_name @@ -160,6 +215,15 @@ module XGen raise "Error with drop_index command: #{doc.inspect}" unless ok?(doc) end + # Return an array of hashes, one for each index on +collection_name+. + # Normally called by Collection#index_information. Each hash contains: + # + # :name :: Index name + # + # :keys :: Hash whose keys are the names of the fields that make up + # the key and values are integers. + # + # :ns :: Namespace; same as +collection_name+. def index_information(collection_name) sel = {:ns => full_coll_name(collection_name)} # TODO synchronize @@ -179,6 +243,9 @@ module XGen } end + # Create a new index on +collection_name+ named +index_name+. +fields+ + # should be an array of field names. Normally called by + # Collection#create_index. def create_index(collection_name, index_name, fields) sel = {:name => index_name, :ns => full_coll_name(collection_name)} field_h = {} @@ -188,6 +255,8 @@ module XGen send_to_db(InsertMessage.new(@name, SYSTEM_INDEX_COLLECTION, sel)) end + # Insert +objects+ into +collection_name+. Normally called by + # Collection#insert. def insert_into_db(collection_name, objects) # TODO synchronize objects.each { |o| send_to_db(InsertMessage.new(@name, collection_name, o)) } @@ -203,6 +272,7 @@ module XGen protected + # Return +true+ if +doc+ contains an 'ok' field with the value 1. def ok?(doc) ok = doc['ok'] ok.kind_of?(Numeric) && ok.to_i == 1 diff --git a/lib/mongo/mongo.rb b/lib/mongo/mongo.rb index 773c1b4..654effd 100644 --- a/lib/mongo/mongo.rb +++ b/lib/mongo/mongo.rb @@ -1,3 +1,4 @@ +# --- # Copyright (C) 2008 10gen Inc. # # This program is free software: you can redistribute it and/or modify it @@ -11,6 +12,7 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +# +++ require 'mongo/db' @@ -18,22 +20,27 @@ module XGen module Mongo module Driver + # Represents a Mongo database server. class Mongo DEFAULT_PORT = 27017 + # Host default is 'localhost', port default is DEFAULT_PORT. def initialize(host='localhost', port=DEFAULT_PORT) @host, @port = host, port end + # Return the XGen::Mongo::Driver::DB named +db_name+. def db(db_name) XGen::Mongo::Driver::DB.new(db_name, @host, @port) end + # Not implemented. def clone_database(from) raise "not implemented" end + # Not implemented. def copy_database(from_host, from_db, to_db) raise "not implemented" end diff --git a/lib/mongo/objectid.rb b/lib/mongo/objectid.rb index b7678dc..0b30115 100644 --- a/lib/mongo/objectid.rb +++ b/lib/mongo/objectid.rb @@ -1,3 +1,4 @@ +# --- # Copyright (C) 2008 10gen Inc. # # This program is free software: you can redistribute it and/or modify it @@ -11,6 +12,7 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +# +++ require 'mutex_m' require 'mongo/util/byte_buffer' @@ -19,7 +21,12 @@ module XGen module Mongo module Driver - # Implementation of the Babble OID. + # Implementation of the Babble OID. Object ids are not required by + # Mongo, but they make certain operations more efficient. + # + # The driver does not automatically assign ids to records that are + # inserted. (An upcoming feature will allow you to give an id "factory" + # to a database and/or a collection.) # # 12 bytes # --- @@ -65,6 +72,7 @@ module XGen @data.collect { |b| '%02x' % b }.join end + # (Would normally be private, but isn't so we can test it.) def generate_id(t=nil) t ||= Time.new.to_i buf = ByteBuffer.new @@ -80,6 +88,7 @@ module XGen buf.to_a.dup end + # (Would normally be private, but isn't so we can test it.) def index_for_time(t) LOCK.mu_synchronize { if t != @@index_time diff --git a/lib/mongo/query.rb b/lib/mongo/query.rb index e95ab23..5309c00 100644 --- a/lib/mongo/query.rb +++ b/lib/mongo/query.rb @@ -1,3 +1,4 @@ +# --- # Copyright (C) 2008 10gen Inc. # # This program is free software: you can redistribute it and/or modify it @@ -11,6 +12,7 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +# +++ require 'socket' require 'mongo/collection' @@ -20,28 +22,52 @@ module XGen module Mongo module Driver + # A query against a collection. A query's selector is a hash. See the + # Mongo documentation for query details. class Query attr_accessor :number_to_skip, :number_to_return, :order_by attr_reader :selector, :fields # writers defined below + # sel :: A hash describing the query. See the Mongo docs for details. + # + # return_fields :: If not +nil+, an array of field names. Only those + # fields will be returned. (Called :fields in calls + # to Collection#find.) + # + # number_to_skip :: Number of records to skip before returning + # records. (Called :offset in calls to + # Collection#find.) + # + # number_to_return :: Max number of records to return. (Called :limit + # in calls to Collection#find.) + # + # order_by :: If not +nil+, specifies record return order. Either hash + # of field names as keys and 1/-1 as values; 1 == + # ascending, -1 == descending, or array of field names + # (all assumed to be sorted in ascending order). (Called + # :sort in calls to Collection#find.) def initialize(sel={}, return_fields=nil, number_to_skip=0, number_to_return=0, order_by=nil) @number_to_skip, @number_to_return, @order_by = number_to_skip, number_to_return, order_by self.selector = sel self.fields = return_fields end + # Set query selector hash. If sel is a string, it will be used as a + # $where clause. (See Mongo docs for details.) def selector=(sel) @selector = case sel when nil {} when String - {"$where" => "function() { return #{sel}; }"} + {"$where" => sel} when Hash sel end end + # Set fields to return. If +val+ is +nil+ or empty, all fields will be + # returned. def fields=(val) @fields = val @fields = nil if @fields && @fields.empty? diff --git a/lib/mongo/util/bson.rb b/lib/mongo/util/bson.rb index 6632d01..87dff7d 100644 --- a/lib/mongo/util/bson.rb +++ b/lib/mongo/util/bson.rb @@ -1,9 +1,24 @@ +# --- +# Copyright (C) 2008 10gen Inc. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License, version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License +# for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +++ + require 'mongo/util/byte_buffer' require 'mongo/util/ordered_hash' require 'mongo/objectid' -# See http://github.com/10gen/mongo/tree/master/db/jsobj.h - +# A BSON seralizer/deserializer. class BSON MINKEY = -1 diff --git a/lib/mongo/util/byte_buffer.rb b/lib/mongo/util/byte_buffer.rb index dcede06..675f8ba 100644 --- a/lib/mongo/util/byte_buffer.rb +++ b/lib/mongo/util/byte_buffer.rb @@ -1,3 +1,20 @@ +# --- +# Copyright (C) 2008 10gen Inc. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License, version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License +# for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +++ + +# A byte buffer. class ByteBuffer attr_reader :order diff --git a/lib/mongo/util/ordered_hash.rb b/lib/mongo/util/ordered_hash.rb index 343a223..af402ce 100644 --- a/lib/mongo/util/ordered_hash.rb +++ b/lib/mongo/util/ordered_hash.rb @@ -1,3 +1,20 @@ +# --- +# Copyright (C) 2008 10gen Inc. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License, version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License +# for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +++ + +# A hash in which the order of keys are preserved. class OrderedHash < Hash attr_accessor :ordered_keys