Current section
Files
Jump to
Current section
Files
lib/mongo_find.ex
defmodule Mongo.Find do
@moduledoc """
Find operation on MongoDB
"""
use Mongo.Helpers
require Record
Record.defrecordp :find, __MODULE__ ,
mongo: nil,
collection: nil,
selector: %{},
projector: %{},
batchSize: 0,
skip: 0,
opts: %{},
mods: %{}
@doc """
Creates a new find operation.
Not to be used directly, prefer `Mongo.Collection.find/3` that returns a `Mongo.Cursor`
"""
def new(collection, jsString, projector) when is_binary(jsString), do: new(collection, %{'$where': jsString}, projector)
def new(collection, selector, projector) do
find(collection: collection, selector: selector, projector: projector, opts: collection.read_opts)
end
@doc """
Sets where MongoDB begins returning results
Must be run before executing the query
iex> Mongo.connect.db("test").collection("anycoll").find.skip(1).toArray |> Enum.count
5
iex> Mongo.connect.db("test").collection("anycoll").find.skip(2).toArray |> Enum.count
4
"""
def skip(n, f), do: find(f, skip: n)
@doc """
Specifies the number of documents to return in each batch
Must be run before executing the query
"""
def batchSize(n, f), do: find(f, batchSize: n)
@doc """
Stream documents retreived from a find query
The following 2 statements are equivalent:
Mongo.connect.db("test").collection("anycoll").find.stream |> Enum.to_list
Mongo.connect.db("test").collection("anycoll").find.toArray
see `Mongo.Cursor.stream/2`
"""
defdelegate stream(find), to: Mongo.Cursor
@doc false
defdelegate batchStream(find), to: Mongo.Cursor
@doc """
Returns a list of documents retreived from a find query
see `Mongo.Cursor.toArray/2`
"""
defdelegate toArray(find), to: Mongo.Cursor
@doc false
defdelegate batchArray(find), to: Mongo.Cursor
@doc """
Executes the query and returns a `Mongo.Cursor`
"""
def exec(find(collection: collection, batchSize: batchSize)=f) do
mongo = collection.db.mongo
mongo |> Mongo.Request.query(f).send
case mongo.response do
{:ok, initialResponse} ->
{:ok, initialResponse, Mongo.Cursor.new(collection, initialResponse, batchSize)}
error -> error
end
end
@doc """
Runs the explain operator that provides information on the query plan
"""
def explain(find(collection: collection)=f) do
mongo = collection.db.mongo
mongo |> Mongo.Request.query(f.addSpecial(:'$explain', 1)).send
case mongo.response do
{:ok, response} -> response.cmd
error -> error
end
end
defbang explain(find)
@doc """
Add hint opperator that forces the query optimizer to use a specific index to fulfill the query
"""
def hint(indexName, f) when is_atom(indexName), do: f.addSpecial(:'$hint', indexName)
def hint(hints, f) when is_map(hints), do: f.addSpecial(:'$hint', hints)
@doc """
Sets query options
Defaults option set is equivalent of calling:
Find.opts(
awaitdata: false
nocursortimeout: false
slaveok: true
tailablecursor: false)
"""
def opts(options, f), do: find(f, opts: options)
def addSpecial(k, v, find(mods: mods)=f) do
find(f, mods: Map.put(mods, k, v))
end
@query <<0xd4, 0x07, 0, 0>> # 2004 query a collection
@query_opts <<0b00000100::8>> # default query options, equvalent to `cursor.set_opts(slaveok: true)`
@doc """
Builds a query message
* collection: collection
* selector: selection criteria (Map or nil)
* projector: fields (Map or nil)
"""
def query(find(collection: collection, selector: selector, projector: projector, skip: skip, batchSize: batchSize, opts: opts, mods: mods)) do
selector = if mods == %{}, do: selector, else: Map.put(mods, :'$query', selector)
@query <> (Enum.reduce(opts, @query_opts, &queryopt_red/2)) <> <<0::24>> <>
collection.db.name <> "." <> collection.name <> <<0::8>> <>
Bson.int32(skip) <>
Bson.int32(batchSize) <>
Bson.encode(selector) <>
Bson.encode(projector)
end
use Bitwise
# Operates one option
defp queryopt_red({opt, true}, bits), do: bits ||| queryopt(opt)
defp queryopt_red({opt, false}, bits), do: bits &&& ~~~queryopt(opt)
defp queryopt_red(_, bits), do: bits
# Identifies the bit that is switched by an option when it is set to `true`
defp queryopt(:awaitdata), do: 0b00100000
defp queryopt(:nocursortimeout), do: 0b00010000
defp queryopt(:slaveok), do: 0b00000100
defp queryopt(:tailablecursor), do: 0b00000010
defp queryopt(_), do: 0b00000000
end