Permalink
Browse files

Documents are now tuples instead of lists

  • Loading branch information...
1 parent 4ef9e7b commit 0a8cc8cafd32c6f09dbee7982abd5453eed8539e Tony Hannan committed Nov 25, 2010
Showing with 46 additions and 42 deletions.
  1. +12 −12 README.md
  2. +8 −5 src/mongo.erl
  3. +3 −2 src/mongo_query.erl
  4. +2 −2 src/mongodb_app.erl
  5. +21 −21 src/mongodb_tests.erl
View
@@ -2,7 +2,7 @@ This is the MongoDB driver for Erlang. [MongoDB](http://www.mongodb.org) is a do
This first version of the driver only supports individual connections to single servers, although multiple processes can use the same connection simultaneously without interfering with each other. This version does not support connection pooling and smart replica-set connection, which will be included in the next version coming out in a couple of weeks.
-This driver is implemented as an Erlang application named *mongodb*. It depends on another Erlang library application named [*bson*](http://github.com/TonyGen/bson-erlang), which defines the document type and its standard binary representation. You need to download both of these applications. Below we describe the mongodb application; you will also need to read the bson documentation to understand the document type.
+This driver is implemented as an Erlang application named *mongodb*. It depends on another Erlang library application named [*bson*](http://github.com/TonyGen/bson-erlang), which defines the document type and its standard binary representation. You need to download both of these applications. Below we describe the mongodb application; you should also read the bson documentation to understand the document type.
Once downloaded, compile each application
@@ -26,31 +26,31 @@ Start a mongodb server listening on `localhost:27017` (or any address & port of
> {ok, Conn} = mongo:connect ({localhost, 27017}).
-`27017` is the default port so you could elide it in this case and just supply `localhost` as the argument. `mongo:connect` will return `{error, Reason}` if it failed to connect.
+`27017` is the default port so you could elide it in this case and just supply `localhost` as the argument. `mongo:connect` returns `{error, Reason}` if it failed to connect.
-A database operation happens with respect to a connection, database, read-mode, and write-mode. These four parameters are supplied once at the beginning of a sequence of read/write operations. Furthermore, if one of the operations fails no further operations in the sequence are executed and an error is returned for the entire sequence.
+A database operation happens in the context of a connection, database, read-mode, and write-mode. These four parameters are supplied once at the beginning of a sequence of read/write operations. Furthermore, if one of the operations fails no further operations in the sequence are executed and an error is returned for the entire sequence.
> mongo:do (safe, master, Conn, test, fun() ->
- mongo:delete (foo, []),
- mongo:insert (foo, [x,1]),
- mongo:find (foo, [x,1]) end).
+ mongo:delete (foo, {}),
+ mongo:insert (foo, {x,1, y,2}),
+ mongo:find (foo, {x,1}) end).
-`safe` is a write-mode; `{safe, GetLastErrorParams}` and `unsafe` are the other write-modes. Safe mode makes a *getLastError* request after every write in the sequence, and if the reply says it failed then the rest of the sequence is aborted and `mongo:do` returns `{error, {write_failure, Reason}}`. Some possible write failures are: attempting to write to a slave server (connected server must be a master), and attempting to insert a duplicate key that is indexed to be unique. Unsafe mode issues every write without a confirmation, so if a write fails (even if the connection fails) you won't know about it and remaining operations will be executed. This is unsafe but faster because you there is no round-trip delay.
+`safe`, along with `{safe, GetLastErrorParams}` and `unsafe`, are write-modes. Safe mode makes a *getLastError* request after every write in the sequence, and if the reply says it failed then the rest of the sequence is aborted and `mongo:do` returns `{error, {write_failure, Reason}}`. Some possible write failures are: attempting to write to a slave server (connected server must be a master), and attempting to insert a duplicate key that is indexed to be unique. Alternatively, unsafe mode issues every write without a confirmation, so if a write fails you won't know about it and remaining operations will be executed. This is unsafe but faster because you there is no round-trip delay.
-`master` is a read-mode; `slave_ok` is the other read-mode. Master means every query in the sequence must read fresh data (from a master/primary server). If the connected server is not a master then the first read will fail, the remaining operations will be aborted, and `mongo:do` will return {error, not_master} [1]. Slave-ok means every query is allowed to read stale data from a slave (master is fine too).
+`master`, along with `slave_ok`, are read-modes. `master` means every query in the sequence must read fresh data (from a master/primary server). If the connected server is not a master then the first read will fail, the remaining operations will be aborted, and `mongo:do` will return {error, not_master} [1]. `slave_ok` means every query is allowed to read stale data from a slave (master is fine too).
`Conn` is the connection we send the operations to. If it fails during one of the operations then the remaining operations are aborted and `{error, {connection_failure, Reason}}` is returned.
-`test` is the name of the database in this example. All collections accessed (`foo` in this example) are taken from the database given in the fourth argument. If a collection is missing from a database it will be automatically created upon first access.
+`test` is the name of the database in this example. Collections accessed in the sequence (`foo` in this example) are taken from the database given in the fourth argument. If a collection is missing from the database it will be automatically created upon first access.
If there are no errors in the sequence of operations then the result of the last operation is returned as the result of the entire `mongo:do` command. It is wrapped in an *ok* tuple as in `{ok, Result}` to distinguish it from an error.
-`mongo:find` returns a *cursor* holding the pending list of results. They are accessed using `mongo:next` to get the next result, and `mongo:rest` to get the remaining results. `mongo:rest` also closes the cursor, otherwise you should close the cursor when finished using `mongo:close_cursor`.
+`mongo:find` returns a *cursor* holding the pending list of results, which are accessed using `mongo:next` to get the next result, and `mongo:rest` to get the remaining results. `mongo:rest` also closes the cursor, otherwise you should close the cursor when finished using `mongo:close_cursor`.
Finally, you should close the connection when finished using `mongo:disconnect`.
-See the [*mongo* module](http://github.com/TonyGen/mongodb-erlang/blob/master/src/mongo.erl) for details on each action such as `update`, `find` and `command`. A type specification is provided with each function so you know the expected arguments and results. The spec line also has a comment if it performs a side-effect such as IO and what exceptions it may throw. No comment means it is a pure function. Also, see the [*bson* module](http://github.com/TonyGen/bson-erlang/blob/master/src/bson.erl) in the bson application for details on the document type and value types.
+See the [*mongo* module](http://github.com/TonyGen/mongodb-erlang/blob/master/src/mongo.erl) for a description of all operations. A type specification is provided with each operation so you know the expected arguments and results. The spec line also has a comment if it performs a side-effect such as IO and what exceptions it may throw. No comment means it is a pure function. Also, see the [*bson* module](http://github.com/TonyGen/bson-erlang/blob/master/src/bson.erl) in the bson application for details on the document type and its value types.
-This driver does not provide helper functions for commands. Use `mongo:command` directly and refer to the [MongoDB documentation](http://www.mongodb.org/display/DOCS/Commands) for how to issue raw commands. A future version will probably include helper functions for the most common commands. In particular, `ensure_index` will be added in the next minor revision because it doesn't even use a command but requires updating a meta-collection directly. In the meantime, you should create indexes from the mongo (javascript) shell.
+This driver does not provide helper functions for commands. Use `mongo:command` directly and refer to the [MongoDB documentation](http://www.mongodb.org/display/DOCS/Commands) for how to issue raw commands. A future version will probably include helper functions for the most common commands. In particular, `ensure_index` will be added in the next minor revision (expected in a few days) because it doesn't even use a command but requires updating a meta-collection directly. In the meantime, you should create indexes from the mongo (javascript) shell.
[1] Currently not-master exception will raise an erlang:error instead of being returned as an error from `mongo:do`. This is a bug that will be fixed in the next minor revision expected in a few days.
View
@@ -83,7 +83,7 @@ write (Write) ->
case Context #context.write_mode of
unsafe -> mongo_query:write (Context #context.dbconn, Write);
SafeMode ->
- Params = case SafeMode of safe -> []; {safe, Param} -> Param end,
+ Params = case SafeMode of safe -> {}; {safe, Param} -> Param end,
Ack = mongo_query:write (Context #context.dbconn, Write, Params),
case bson:lookup (err, Ack) of
{} -> ok; {null} -> ok;
@@ -104,13 +104,13 @@ insert_all (Coll, Docs) ->
% If doc has no '_id' field then generate a fresh object id for it
assign_id (Doc) -> case bson:lookup ('_id', Doc) of
{_Value} -> Doc;
- {} -> ['_id', mongodb_app:gen_objectid() | Doc] end.
+ {} -> bson:append ({'_id', mongodb_app:gen_objectid()}, Doc) end.
-spec save (collection(), bson:document()) -> ok. % Action
% If document has no '_id' field then insert it, otherwise update it and insert only if missing.
save (Coll, Doc) -> case bson:lookup ('_id', Doc) of
{} -> insert (Coll, Doc);
- {Id} -> repsert (Coll, ['_id', Id], Doc) end.
+ {Id} -> repsert (Coll, {'_id', Id}, Doc) end.
-spec replace (collection(), selector(), bson:document()) -> ok. % Action
% Replace first document selected with given document.
@@ -209,8 +209,11 @@ count (Coll, Selector) -> count (Coll, Selector, 0).
-spec count (collection(), selector(), integer()) -> integer(). % Action
% Count selected documents up to given max number; 0 means no max. Ie. stops counting when max is reached to save processing time.
count (Coll, Selector, Limit) ->
- LimitDoc = if Limit =< 0 -> []; true -> [limit, Limit] end,
- Doc = command ([count, atom_to_binary (Coll, utf8), 'query', Selector | LimitDoc]),
+ CollStr = atom_to_binary (Coll, utf8),
+ Command = if
+ Limit =< 0 -> {count, CollStr, 'query', Selector};
+ true -> {count, CollStr, 'query', Selector, limit, Limit} end,
+ Doc = command (Command),
trunc (bson:at (n, Doc)). % Server returns count as float
% Command %
View
@@ -23,8 +23,9 @@
-spec write (mongo_connect:dbconnection(), write(), getlasterror_request()) -> getlasterror_reply(). % CIO
% Send write and getlasterror request to mongodb over connection and wait for and return getlasterror reply. Bad getlasterror params are ignored.
% Caller is responsible for checking for error in reply; if 'err' field is null then success, otherwise it holds error message string.
-write (DbConn, Write, GetLastErrorParams) ->
- Query = #'query' {batchsize = -1, collection = '$cmd', selector = [getlasterror, 1 | GetLastErrorParams]},
+write (DbConn, Write, GetlasterrorParams) ->
+ Query = #'query' {batchsize = -1, collection = '$cmd',
+ selector = bson:append ({getlasterror, 1}, GetlasterrorParams)},
Reply = mongo_connect:call (DbConn, [Write], Query),
{0, [Doc | _]} = query_reply (Reply),
Doc.
View
@@ -35,10 +35,10 @@ next_requestid() -> ets:update_counter (?MODULE, requestid_counter, 1).
-spec gen_objectid () -> bson:objectid(). % IO
% Fresh object id
gen_objectid() ->
- {unixtime, Now} = bson:timenow(),
+ Now = bson:unixtime_to_secs (bson:timenow()),
MPid = ets:lookup_element (?MODULE, oid_machineprocid, 2),
N = ets:update_counter (?MODULE, oid_counter, 1),
- bson:objectid (Now div 1000, MPid, N).
+ bson:objectid (Now, MPid, N).
-spec oid_machineprocid () -> <<_:40>>. % IO
% Fetch hostname and os pid and compress into a 5 byte id
View
@@ -18,45 +18,45 @@ test() -> eunit:test ({setup,
% This test must be run first right after application start (assumes vars contain initial value)
app_test() ->
1 = mongodb_app:next_requestid(),
- {unixtime, Ms} = bson:timenow(),
- {oid, <<T3, T2, _:80>>} = bson:objectid (Ms div 1000, <<1,2,3,4,5>>, 0),
- {oid, <<T3, T2, _:56, 1:24/big>>} = mongodb_app:gen_objectid(). % high two timestamp bytes should match
+ UnixSecs = bson:unixtime_to_secs (bson:timenow()),
+ {<<T3, T2, _:80>>} = bson:objectid (UnixSecs, <<1,2,3,4,5>>, 0),
+ {<<T3, T2, _:56, 1:24/big>>} = mongodb_app:gen_objectid(). % high two timestamp bytes should match
% Mongod server must be running on 127.0.0.1:27017
connect_test() ->
{ok, Conn} = mongo_connect:connect ({"127.0.0.1", 27017}),
DbConn = {test, Conn},
- Res = mongo_query:write (DbConn, #delete {collection = foo, selector = []}, []),
+ Res = mongo_query:write (DbConn, #delete {collection = foo, selector = {}}, {}),
{null} = bson:lookup (err, Res),
- Doc0 = ['_id', 0, text, <<"hello">>],
- Doc1 = ['_id', 1, text, <<"world">>],
- Res1 = mongo_query:write (DbConn, #insert {collection = foo, documents = [Doc0, Doc1]}, []),
+ Doc0 = {'_id', 0, text, <<"hello">>},
+ Doc1 = {'_id', 1, text, <<"world">>},
+ Res1 = mongo_query:write (DbConn, #insert {collection = foo, documents = [Doc0, Doc1]}, {}),
{null} = bson:lookup (err, Res1),
- ok = mongo_query:write (DbConn, #update {collection = foo, selector = ['_id', 1], updater = ['$set', [text, <<"world!!">>]]}),
+ ok = mongo_query:write (DbConn, #update {collection = foo, selector = {'_id', 1}, updater = {'$set', {text, <<"world!!">>}}}),
Doc1X = bson:update (text, <<"world!!">>, Doc1),
- Cursor = mongo_query:find (DbConn, #'query' {collection = foo, selector = []}),
+ Cursor = mongo_query:find (DbConn, #'query' {collection = foo, selector = {}}),
[Doc0, Doc1X] = mongo_cursor:rest (Cursor),
mongo_connect:close (Conn).
% Mongod server must be running on 127.0.0.1:27017
mongo_test() ->
{ok, Conn} = mongo:connect ("127.0.0.1"),
mongo:do (safe, master, Conn, baseball, fun () ->
- mongo:delete (team, []),
+ mongo:delete (team, {}),
Teams0 = [
- [name, <<"Yankees">>, home, [city, <<"New York">>, state, <<"NY">>], league, <<"American">>],
- [name, <<"Mets">>, home, [city, <<"New York">>, state, <<"NY">>], league, <<"National">>],
- [name, <<"Phillies">>, home, [city, <<"Philadelphia">>, state, <<"PA">>], league, <<"National">>],
- [name, <<"Red Sox">>, home, [city, <<"Boston">>, state, <<"MA">>], league, <<"American">>] ],
+ {name, <<"Yankees">>, home, {city, <<"New York">>, state, <<"NY">>}, league, <<"American">>},
+ {name, <<"Mets">>, home, {city, <<"New York">>, state, <<"NY">>}, league, <<"National">>},
+ {name, <<"Phillies">>, home, {city, <<"Philadelphia">>, state, <<"PA">>}, league, <<"National">>},
+ {name, <<"Red Sox">>, home, {city, <<"Boston">>, state, <<"MA">>}, league, <<"American">>} ],
Ids = mongo:insert_all (team, Teams0),
- 4 = mongo:count (team, []),
- Teams = lists:zipwith (fun (Id, Team) -> ['_id', Id | Team] end, Ids, Teams0),
- Teams = mongo:rest (mongo:find (team, [])),
+ 4 = mongo:count (team, {}),
+ Teams = lists:zipwith (fun (Id, Team) -> bson:append ({'_id', Id}, Team) end, Ids, Teams0),
+ Teams = mongo:rest (mongo:find (team, {})),
NationalTeams = lists:filter (fun (Team) -> bson:lookup (league, Team) == {<<"National">>} end, Teams),
- NationalTeams = mongo:rest (mongo:find (team, [league, <<"National">>])),
- TeamNames = lists:map (fun (Team) -> [name, bson:at (name, Team)] end, Teams),
- TeamNames = mongo:rest (mongo:find (team, [], ['_id', 0, name, 1])),
+ NationalTeams = mongo:rest (mongo:find (team, {league, <<"National">>})),
+ TeamNames = lists:map (fun (Team) -> {name, bson:at (name, Team)} end, Teams),
+ TeamNames = mongo:rest (mongo:find (team, {}, {'_id', 0, name, 1})),
BostonTeam = lists:last (Teams),
- {BostonTeam} = mongo:find_one (team, [home, [city, <<"Boston">>, state, <<"MA">>]])
+ {BostonTeam} = mongo:find_one (team, {home, {city, <<"Boston">>, state, <<"MA">>}})
end),
mongo:disconnect (Conn).

0 comments on commit 0a8cc8c

Please sign in to comment.