Current section
Files
Jump to
Current section
Files
lib/mongo_response.ex
defmodule Mongo.Response do
@moduledoc """
Receives, decode and parse MongoDB response from the server
"""
use Mongo.Helpers
require Record
Record.defrecordp :response, __MODULE__ ,
cursorID: nil,
startingFrom: nil,
nbDoc: nil,
docBuffer: nil,
requestID: nil,
bufferOffset: 0
@msg <<1, 0, 0, 0>> # 1 Opcode OP_REPLY : Reply to a client request
@doc """
Parses a response message
If the message is partial, this method makes shure the response is complete by fetching additional messages
"""
def new(
<<_::32, # total message size, including this
_::32, # identifier for this message
requestID::size(32)-signed-little, # requestID from the original request
@msg::binary, # Opcode OP_REPLY
_::6, queryFailure::1, cursorNotFound::1, _::24, # bit vector representing response flags
cursorID::size(64)-signed-little, # cursor id if client needs to do get more's
startingFrom::size(32)-signed-little, # where in the cursor this reply is starting
numberReturned::size(32)-signed-little, # number of documents in the reply
docBuffer::bitstring>>) do # buffer of Bson documents
cond do
cursorNotFound>0 ->
{:error, "Cursor not found"}
queryFailure>0 ->
if numberReturned>0 do
{:error, Map.take(Bson.decode(docBuffer), [:'$err', :err, :errmsg, :code, :connectionId])}
else
{:error, "Query error"}
end
true -> {:ok, response( cursorID: cursorID,
startingFrom: startingFrom,
nbDoc: numberReturned,
docBuffer: docBuffer,
requestID: requestID )}
end
end
defbang new(message)
@doc """
Returns `true` if there are more documents to fetch regardless whether the cursor is exhausted or not
"""
def next?(response(nbDoc: 0)), do: false
def next?(_), do: true
@doc """
Gets next doc within the current batch return `nil` after last doc of the bacth
When this function returns `nil`, it does not mean the cursor is exhausted, see `Mongo.Response.hasNext/1`
"""
def next(response(nbDoc: nbDoc, bufferOffset: off, docBuffer: docBuffer)=r) when nbDoc>0 do
# get document part of documents from this offset
partlen = Bson.int32(docBuffer, off)
{Bson.decode({off, partlen}, docBuffer), response(r, nbDoc: nbDoc-1, bufferOffset: off+partlen)}
end
def next(_, response(nbDoc: 0)), do: nil
@doc """
Returns the cursor ID
"""
def cursorID(response(cursorID: cursorID)), do: cursorID
@doc """
Returns `true` if the cursor is exhausted
"""
def exhausted?(response(cursorID: 0)), do: true
def exhausted?(_), do: false
@doc """
Returns `true` if there are more documents to fetch from this batch or if the cursor is not exhausted
"""
def hasNext(r), do: r.next? or not r.exhausted
@doc """
Parse a command response
Returns `{:ok, doc}` or `{:error, reason}`
"""
def cmd(response(nbDoc: 1)=r) do
case r.next do
nil -> {:error, "No document received"}
{doc, _} ->
if doc[:ok] > 0 do
{:ok, doc}
else
{:error, Map.take(doc, [:err, :errmsg, :code, :connectionId])}
end
end
end
@doc """
Parse a count respsonse
Returns `{:ok, n}` or `{:error, reason}`
"""
def count(r) do
case r.cmd do
{:ok, doc} -> {:ok, doc[:n]}
error -> error
end
end
@doc """
Parse a success respsonse
Returns `:ok` or `{:error, reason}`
"""
def success(r) do
case r.cmd do
{:ok, _} -> :ok
error -> error
end
end
@doc """
Parse a distinct respsonse
Returns `{:ok, values}` or `{:error, reason}`
"""
def distinct(r) do
case r.cmd do
{:ok, doc} -> {:ok, doc[:values]}
error -> error
end
end
@doc """
Parse a map-reduce respsonse
Returns `{:ok, results}` (inline) or `:ok` or `{:error, reason}`
"""
def mr(r) do
case r.cmd do
{:ok, doc} ->
case doc[:results] do
nil -> :ok
results -> {:ok, results}
end
error -> error
end
end
@doc """
Parse a group respsonse
Returns `{:ok, retval}` or `{:error, reason}`
"""
def group(r) do
case r.cmd do
{:ok, doc} -> {:ok, doc[:retval]}
error -> error
end
end
@doc """
Parse a aggregate respsonse
Returns `{:ok, result}` or `{:error, reason}`
"""
def aggregate(r) do
case r.cmd do
{:ok, doc} -> doc[:result]
error -> error
end
end
@doc """
Parse a getnonce respsonse
Returns `{:ok, nonce}` or `{:error, reason}`
"""
def getnonce(r) do
case r.cmd do
{:ok, doc} -> doc[:nonce]
error -> error
end
end
@doc """
Parse a error respsonse
Returns `{:ok, nonce}` or `{:error, reason}`
"""
def error(r) do
case r.cmd do
{:ok, doc} ->
case doc[:err] do
nil -> :ok
_ -> {:error, Map.take(doc, [:err, :errmsg, :code, :connectionId])}
end
error -> error
end
end
end