{"id":13529826,"url":"https://github.com/zookzook/elixir-mongodb-driver","last_synced_at":"2025-05-14T15:02:26.320Z","repository":{"id":34580142,"uuid":"180210180","full_name":"zookzook/elixir-mongodb-driver","owner":"zookzook","description":"MongoDB driver for Elixir","archived":false,"fork":false,"pushed_at":"2025-02-11T18:54:39.000Z","size":5496,"stargazers_count":256,"open_issues_count":5,"forks_count":67,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-04-13T00:40:03.263Z","etag":null,"topics":["bulk","change-streams","driver","elixir","mongodb","sessions","transactions"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zookzook.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-04-08T18:33:16.000Z","updated_at":"2025-03-27T07:46:17.000Z","dependencies_parsed_at":"2023-02-18T22:45:26.190Z","dependency_job_id":"b3d35f80-af18-4887-86e8-10bcfe8cc5fd","html_url":"https://github.com/zookzook/elixir-mongodb-driver","commit_stats":{"total_commits":861,"total_committers":85,"mean_commits":"10.129411764705882","dds":0.5400696864111498,"last_synced_commit":"e989801d5c9c49876be02a729477c9246e721128"},"previous_names":[],"tags_count":47,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zookzook%2Felixir-mongodb-driver","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zookzook%2Felixir-mongodb-driver/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zookzook%2Felixir-mongodb-driver/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zookzook%2Felixir-mongodb-driver/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zookzook","download_url":"https://codeload.github.com/zookzook/elixir-mongodb-driver/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254168657,"owners_count":22026206,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bulk","change-streams","driver","elixir","mongodb","sessions","transactions"],"created_at":"2024-08-01T07:00:39.747Z","updated_at":"2025-05-14T15:02:26.230Z","avatar_url":"https://github.com/zookzook.png","language":"Elixir","funding_links":[],"categories":["ORM and Datamapping","Libraries"],"sub_categories":["Elixir"],"readme":"# The Elixir Driver for MongoDB\n\n[![Hex.pm](https://img.shields.io/hexpm/v/mongodb_driver.svg)](https://hex.pm/packages/mongodb_driver)\n[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/mongodb_driver/)\n[![Hex.pm](https://img.shields.io/hexpm/dt/mongodb_driver.svg)](https://hex.pm/packages/mongodb_driver)\n[![Hex.pm](https://img.shields.io/hexpm/dw/mongodb_driver.svg)](https://hex.pm/packages/mongodb_driver)\n[![Hex.pm](https://img.shields.io/hexpm/dd/mongodb_driver.svg)](https://hex.pm/packages/mongodb_driver)\n[![License](https://img.shields.io/hexpm/l/mongodb_driver.svg)](https://github.com/zookzook/elixir-mongodb-driver/blob/master/LICENSE)\n[![Last Updated](https://img.shields.io/github/last-commit/zookzook/elixir-mongodb-driver.svg)](https://github.com/zookzook/elixir-mongodb-driver/commits/master)\n\n## Features\n\n- supports MongoDB versions 4.x, 5.x, 6.x, 7.x, 8.x\n- connection pooling ([through DBConnection 2.x](https://github.com/elixir-ecto/db_connection))\n- streaming cursors\n- performant ObjectID generation\n- aggregation pipeline\n- replica sets\n- support for SCRAM-SHA-256 (MongoDB 4.x)\n- support for GridFS ([See](https://github.com/mongodb/specifications/blob/master/source/gridfs/gridfs-spec.md))\n- support for change streams api ([See](https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.md))\n- support for bulk writes ([See](https://github.com/mongodb/specifications/blob/master/source/crud/crud.md#write))\n- support for driver sessions ([See](https://github.com/mongodb/specifications/blob/master/source/sessions/driver-sessions.md))\n- support for driver transactions ([See](https://github.com/mongodb/specifications/blob/master/source/transactions/transactions.md))\n- support for command monitoring ([See](https://github.com/mongodb/specifications/blob/master/source/command-logging-and-monitoring/command-logging-and-monitoring.md))\n- support for retryable reads ([See](https://github.com/mongodb/specifications/blob/master/source/retryable-reads/retryable-reads.md))\n- support for retryable writes ([See](https://github.com/mongodb/specifications/blob/master/source/retryable-writes/retryable-writes.md))\n- support for simple structs using the Mongo.Encoder protocol\n- support for complex and nested documents using the `Mongo.Collection` macros\n- support for streaming protocol ([See](https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-monitoring.md#streaming-protocol))\n- support for migration scripts\n- support for compression for zlib and zstd ([See](https://github.com/mongodb/specifications/blob/07b7649cc5c805ef4f85fccddf39226add7114e6/source/compression/OP_COMPRESSED.md))\n\n## Usage\n\n### Installation\n\nAdd `mongodb_driver` to your mix.exs `deps`.\n\n```elixir\ndefp deps do\n  [{:mongodb_driver, \"~\u003e 1.5.0\"}]\nend\n```\n\nThen run `mix deps.get` to fetch dependencies.\n\n### Simple Connection to MongoDB\n\n```elixir\n# Starts an unpooled connection\n{:ok, top} = Mongo.start_link(url: \"mongodb://localhost:27017/my-database\")\n\ntop\n|\u003e Mongo.find(\"test-collection\", %{})\n|\u003e Enum.to_list()\n```\n\nTo specify a username and password, use the `:username`, `:password`, and `:auth_source` options.\n\n```elixir\n# Starts an unpooled connection\n{:ok, top} =\n    Mongo.start_link(url: \"mongodb://localhost:27017/db-name\",\n                     username: \"test_user\",\n                     password: \"hunter2\",\n                     auth_source: \"admin_test\")\n\ntop\n|\u003e Mongo.find(\"test-collection\", %{})\n|\u003e Enum.to_list()\n```\n\nFor secure requests, you may need to add some more options; see the \"AWS, TLS and Erlang SSL ciphers\" section below.\n\nFailing operations return a `{:error, error}` tuple where `error` is a\n`Mongo.Error` object:\n\n```elixir\n{:error,\n %Mongo.Error{\n   code: 13435,\n   error_labels: [],\n   host: nil,\n   message: \"not master and slaveOk=false\",\n   resumable: true,\n   retryable_reads: true,\n   retryable_writes: true\n }}\n```\n\n## Examples\n\n### Find\n\nUsing `$and`\n\n```elixir\n@topology\n|\u003e Mongo.find(\"users\", %{\"$and\" =\u003e [%{email: \"my@email.com\"}, %{first_name: \"first_name\"}]})\n|\u003e Enum.to_list()\n```\n\nUsing `$or`\n\n```elixir\n@topology\n|\u003e Mongo.find(\"users\", %{\"$or\" =\u003e [%{email: \"my@email.com\"}, %{first_name: \"first_name\"}]})\n|\u003e Enum.to_list()\n```\n\nUsing `$in`\n\n```elixir\n@topology\n|\u003e Mongo.find(\"users\", %{email: %{\"$in\" =\u003e [\"my@email.com\", \"other@email.com\"]}})\n|\u003e Enum.to_list()\n```\n\n## How to use the `Mongo.Stream`?\n\nMost query functions return a `Mongo.Stream` struct that implements the `Enumerable` protocol. The module checks out \nthe session and streams the batches from the server until the last batch has been received. \nThe session is then checked in for reuse. [Sessions](https://github.com/mongodb/specifications/blob/master/source/sessions/driver-sessions.md) are \ntemporary and reusable data structures, e.g. to support transactions. They are required by the Mongo DB driver specification.\n\nThe use of internal structures of the `Mongo.Stream` struct is therefore not planned. For example, the following code results in an open session and the `docs` will only contain the first batch:\n\n```elixir\n%Mongo.Stream{docs: docs} = Mongo.aggregate(@topology, collection, pipeline, opts)\nEnum.map(docs, fn elem -\u003e elem end)\n```\n\nThe `Mongo.Stream` struct should therefore always be processed by an `Enum` or `Stream` function so that the session management \ncan take place automatically:\n\n```elixir\n@topology\n|\u003e Mongo.aggregate(collection, pipeline, opts)\n|\u003e Enum.to_list()\n```\n\n### Inserts\n\nTo insert a single document:\n\n```elixir\nMongo.insert_one(top, \"users\", %{first_name: \"John\", last_name: \"Smith\"})\n```\n\nTo insert a list of documents:\n\n```elixir\nMongo.insert_many(top, \"users\", [\n  %{first_name: \"John\", last_name: \"Smith\"},\n  %{first_name: \"Jane\", last_name: \"Doe\"}\n])\n```\n\n## mongodb_ecto\n\nThe version 1.4.0 supports the [mongodb_ecto](https://github.com/elixir-mongo/mongodb_ecto) package.\nA series of changes are required to support the adapter. Some BSON encoders and a missing generic update function were added for the adapter.\nMost notably, the `find-then-modify` command functions `find_one_and_update` and `find_one_and_replace` now return appropriate\n`FindAndModifyResult` structs that contain additional write information otherwise neglected, which the adapter requires.\n\nAfter upgrading the driver to version 1.4.0 you need to change the code regarding the results of\n* `Mongo.find_one_and_update`\n* `Mongo.find_one_and_replace`\n\n## Data Representation\n\nThis driver chooses to accept both maps and lists of key-value tuples when encoding BSON documents (1), but will only\ndecode documents into maps. Maps are convenient to work with, but Elixir map keys are not ordered, unlike BSON document\nkeys.\n\nThat design decision means document key order is lost when encoding Elixir maps to BSON and, conversely, when decoding\nBSON documents to Elixir maps. However, see [Preserve Document Key Order](#preserve-document-key-order) to learn how to\npreserve key order when it matters.\n\nAdditionally, the driver accepts both atoms and strings for document keys, but will only decode them into strings.\nCreating atoms from arbitrary input (such as database documents) is\n[discouraged](https://elixir-lang.org/getting-started/mix-otp/genserver.html#:~:text=However%2C%20naming%20dynamic,our%20system%20memory!)\nbecause atoms are not garbage collected.\n\n[BSON symbols (deprecated)](https://bsonspec.org/spec.html#:~:text=Symbol.%20%E2%80%94%20Deprecated) can only be decoded (2).\n\n    BSON                Elixir\n    ----------          ------\n    double              0.0\n    string              \"Elixir\"\n    document            [{\"key\", \"value\"}] | %{\"key\" =\u003e \"value\"} (1)\n    binary              %BSON.Binary{binary: \u003c\u003c42, 43\u003e\u003e, subtype: :generic}\n    UUID                %BSON.Binary{binary: \u003c\u003c42, 43\u003e\u003e, subtype: :uuid}\n    UUID (old style)    %BSON.Binary{binary: \u003c\u003c42, 43\u003e\u003e, subtype: :uuid_old}\n    object id           %BSON.ObjectId{value: \u003c\u003c...\u003e\u003e}\n    boolean             true | false\n    UTC datetime        %DateTime{}\n    null                nil\n    regex               %BSON.Regex{pattern: \"...\"}\n    JavaScript          %BSON.JavaScript{code: \"...\"}\n    timestamp           #BSON.Timestamp\u003cvalue:ordinal\u003e\"\n    integer 32          42\n    integer 64          #BSON.LongNumber\u003cvalue\u003e\n    symbol              \"foo\" (2)\n    min key             :BSON_min\n    max key             :BSON_max\n    decimal128          Decimal{}\n\n## Preserve Document Key Order\n\n### Encoding from Elixir to BSON\n\nFor some MongoDB operations, the order of the keys in a document affect the result. For example, that is the case when\nsorting a query by multiple fields.\n\nIn those cases, driver users should represent documents using a list of tuples (or a keyword list) to preserve the\norder. Example:\n\n```elixir\n@topology\n|\u003e Mongo.find(\"users\", %{}, sort: [last_name: 1, first_name: 1, _id: 1])\n|\u003e Enum.to_list()\n```\n\nThe query above will sort users by last name, then by first name and finally by ID. If an Elixir map had been used to\nspecify `:sort`, query results would end up sorted unexpectedly wrong.\n\n### Decoding from BSON to Elixir\n\nDecoded BSON documents are always represented by Elixir maps because the driver depends on that to implement its\nfunctionality.\n\nIf the order of document keys as stored by MongoDB is needed, the driver can be configured to use a BSON decoder module\nthat puts a list of keys in the original order under the `:__order__` key (and it works recursively).\n\n```elixir\nconfig :mongodb_driver,\n  decoder: BSON.PreserveOrderDecoder\n```\n\nIt is possible to customize the key. For example, to use `:original_order` instead of the default `:__order__`:\n\n```elixir\nconfig :mongodb_driver,\n  decoder: {BSON.PreserveOrderDecoder, key: :original_order}\n```\n\nThe resulting maps with annotated key order can be recursively transformed into lists of tuples. That allows for\npreserving the order again when encoding. Here is an example of how to achieve that:\n\n```elixir\ndefmodule MapWithOrder do\n  def to_list(doc, order_key \\\\ :__order__) do\n    do_to_list(doc, order_key)\n  end\n\n  defp do_to_list(%{__struct__: _} = elem, _order_key) do\n    elem\n  end\n\n  defp do_to_list(doc, order_key) when is_map(doc) do\n    doc\n    |\u003e Map.get(order_key, Map.keys(doc))\n    |\u003e Enum.map(fn key -\u003e {key, do_to_list(Map.get(doc, key), order_key)} end)\n  end\n\n  defp do_to_list(xs, order_key) when is_list(xs) do\n    Enum.map(xs, fn elem -\u003e do_to_list(elem, order_key) end)\n  end\n\n  defp do_to_list(elem, _order_key) do\n    elem\n  end\nend\n\n# doc = ...\nMapWithOrder.to_list(doc)\n```\n\nNote that structs are kept as-is, to handle special values such as `BSON.ObjectId`.\n\nThe decoder module is defined at compile time. The default decoder is `BSON.Decoder`, which does not preserve document\nkey order. As it needs to execute fewer operations when decoding data received from MongoDB, it offers improved\nperformance. Therefore, the default decoder is recommended for most use cases of this driver.\n\n## Writing your own encoding info\n\nIf you want to write a custom struct to your mongo collection - you can do that\nby implementing `Mongo.Encoder` protocol for your module. The output should be a map,\nwhich will be passed to the Mongo database.\n\nExample:\n\n```elixir\ndefmodule CustomStruct do\n  @fields [:a, :b, :c, :id]\n  @enforce_keys @fields\n  defstruct @fields\n  defimpl Mongo.Encoder do\n    def encode(%{a: a, b: b, id: id}) do\n      %{\n        _id: id,\n        a: a,\n        b: b,\n        custom_encoded: true\n      }\n    end\n  end\nend\n```\n\nSo, given the struct:\n\n```elixir\n%CustomStruct{a: 10, b: 20, c: 30, id: \"5ef27e73d2a57d358f812001\"}\n```\n\nit will be written to database, as:\n\n```json\n{\n  \"a\": 10,\n  \"b\": 20,\n  \"custom_encoded\": true,\n  \"_id\": \"5ef27e73d2a57d358f812001\"\n}\n```\n\n## Collections\n\nWhile using the `Mongo.Encoder` protocol give you the possibility to encode your structs into maps the opposite way to decode those maps into structs is missing. To handle it you can use the `Mongo.Collection` which provides some boilerplate code for a better support of structs while using the MongoDB driver\n\n- automatic load and dump function\n- reflection functions\n- type specification\n- support for embedding one and many structs\n- support for `after load` function\n- support for `before dump` function\n- support for id generation\n- support for default values\n- support for derived values\n- support for alias attribute names\n\nBut in the case of queries and updates, a rewrite of the attribute names does not take place. It is still up to you\nto use the correct attribute names.\n\nWhen using the MongoDB driver only maps and keyword lists are used to represent documents.\nIf you prefer to use structs instead of the maps to give the document a stronger meaning or to emphasize\nits importance, you have to create a `defstruct` and fill it from the map manually:\n\n```elixir\ndefmodule Label do\n  defstruct name: \"warning\", color: \"red\"\nend\n\niex\u003e label_map = Mongo.find_one(:mongo, \"labels\", %{})\n  %{\"name\" =\u003e \"warning\", \"color\" =\u003e \"red\"}\niex\u003e label = %Label{name: label_map[\"name\"], color: label_map[\"color\"]}\n```\n\nWe have defined a module `Label` as `defstruct`, then we get the first label document\nthe collection `labels`. The function `find_one` returns a map. We convert the map manually and\nget the desired struct. If we want to save a new structure, we have to do the reverse. We convert the struct into a map:\n\n```elixir\niex\u003e label = %Label{}\niex\u003e label_map = %{\"name\" =\u003e label.name, \"color\" =\u003e label.color}\niex\u003e {:ok, _} = Mongo.insert_one(:mongo, \"labels\", label_map)\n```\n\nAlternatively, you can also remove the `__struct__` key from `label`. The MongoDB driver automatically\nconverts the atom keys into strings (Or use the `Mongo.Encode` protocol)\n\n```elixir\niex\u003e  Map.drop(label, [:__struct__])\n%{color: :red, name: \"warning\"}\n```\n\nIf you use nested structures, the work becomes a bit more complex. In this case, you have to use the inner structures\nconvert manually, too. If you take a closer look at the necessary work, two basic functions can be derived:\n\n- `load` Conversion of the map into a struct.\n- `dump` Conversion of the struct into a map.\n\n`Mongo.Collection` provides the necessary macros to automate this boilerplate code. The above example can be rewritten as follows:\n\n```elixir\ndefmodule Label do\n    use Mongo.Collection\n\n    document do\n      attribute :name, String.t(), default: \"warning\"\n      attribute :color, String.t(), default: :red\n    end\nend\n```\n\nThis results in the following module:\n\n```elixir\ndefmodule Label do\n\n    defstruct [name: \"warning\", color: \"red\"]\n\n    @type t() :: %Label{String.t(), String.t()}\n\n    def new()...\n    def load(map)...\n    def dump(%Label{})...\n    def __collection__(:attributes)...\n    def __collection__(:types)...\n    def __collection__(:collection)...\n    def __collection__(:id)...\n\nend\n```\n\nYou can now create new structs with the default values and use the conversion functions between map and structs:\n\n```elixir\niex(1)\u003e x = Label.new()\n%Label{color: :red, name: \"warning\"}\niex(2)\u003e m = Label.dump(x)\n%{color: :red, name: \"warning\"}\niex(3)\u003e Label.load(m, true)\n%Label{color: :red, name: \"warning\"}\n```\n\nThe `load/2` function distinguishes between keys of type binarys `load(map, false)` and keys of type atoms `load(map, true)`. The default is `load(map, false)`:\n\n```elixir\niex(1)\u003e m = %{\"color\" =\u003e :red, \"name\" =\u003e \"warning\"}\niex(2)\u003e Label.load(m)\n%Label{color: :red, name: \"warning\"}\n```\n\nIf you would now expect atoms as keys, the result of the conversion is not correct in this case:\n\n```elixir\niex(3)\u003e Label.load(m, true)\n%Label{color: nil, name: nil}\n```\n\nThe background is that MongoDB always returns binarys as keys and structs use atoms as keys.\nFor more information look at the module documentation `Mongo.Collection`.\nOf course, using the `Mongo.Collection` is not free. When loading and saving, the maps are converted into structures, which increases CPU usage somewhat. When it comes to speed, it is better to use the maps directly.\n\n## Breaking changes\n\nPrior to version 0.9.2 the dump function returns atoms as key. Since the `dump/1` function is the inverse function of `load/1`,\nwhich uses binary keys as default, the `dump/1` function should return binary keys as well. This increases the consistency and\nyou can do:\n\n    l = Label.load(doc)\n    doc = Label.dump(l)\n    assert l == Label.load(doc)\n\n## Using the Repo Module\n\nFor convenience, you can also `use` the `Mongo.Repo` module in your application to configure the MongoDB application.\n\nSimply create a new module and include the `use Mongo.Repo` macro:\n\n```elixir\ndefmodule MyApp.Repo do\n  use Mongo.Repo,\n    otp_app: :my_app,\n    topology: :mongo\nend\n```\n\nTo configure the MongoDB add the configuration to your `config.exs`:\n\n```elixir\nconfig :my_app, MyApp.Repo,\n  url: \"mongodb://localhost:27017/my-app-dev\",\n  timeout: 60_000,\n  idle_interval: 10_000,\n  queue_target: 5_000\n```\n\nFinally, we can add the `Mongo.Repo` instance to our application supervision tree:\n\n```elixir\n  children = [\n    # ...\n    MyApp.Repo,\n    # ...\n  ]\n```\n\nIn addition, the convenient configuration, the `Mongo.Repo` module will also include query functions to use with your\n`Mongo.Collection` modules.\n\nFor more information check out the `Mongo.Repo` module documentation and the `Mongo` module documentation.\n\n## Breaking changes\n\nPrior to version 0.9.2 some `Mongo.Repo` functions use the `dump/1` function for the query (and update) parameter. \nThis worked only for some query that used only the attributes of the document. In the case of nested documents, \nit didn't work, so it is changed to be more consistent. The `Mongo.Repo` module is very simple without any query \nrewriting like Ecto does. In the case you want to use the `:name` option, you need to specify the query and update \ndocuments in the `Mongo.Repo` functions following the specification in the MongoDB. Example:\n\n    defmodule MyApp.Session do\n        @moduledoc false\n        use Mongo.Collection\n        \n        alias BSON.Binary\n        \n        collection :s do\n            attribute :uuid, Binary.t(), name: :u \n        end\n    end\n\nIf you use the `Mongo.Repo` module and want to fetch a specific session document, this won't work:\n\n    MyApp.Repo.get_by(MyApp.Session, %{uuid: session_uuid})\n\nbecause the `get_by/2` function uses the query parameter without any rewriting. You need to change the query:\n\n    MyApp.Repo.get_by(MyApp.Session, %{u: session_uuid})\n\nA rewriting is too complex for now because MongoDB has a lot of options. \n\n## Logging\n\nYou config the logging output by adding in your config file this line\n\n```elixir\nconfig :mongodb_driver, log: true\n```\n\nThe attribute `log` supports `true`, `false` or a log level like `:info`. The default value is `false`. If you turn\nlogging on, then you will see log output (command, collection, parameters):\n\n```\n[info] CMD find \"my-collection\" [filter: [name: \"Helga\"]] db=2.1ms\n```\n\n## Telemetry\n\nThe driver uses the [:telemetry](https://github.com/beam-telemetry/telemetry) package to emit the execution duration\nfor each command. The event name is `[:mongodb_driver, :execution]` and the driver uses the following meta data:\n\n```elixir\nmetadata = %{\n    type: :mongodb_driver,\n    command: command,\n    params: parameters,\n    collection: collection,\n    options: Keyword.get(opts, :telemetry_options, [])\n}\n\n:telemetry.execute([:mongodb_driver, :execution], %{duration: duration}, metadata)\n```\n\nIn a Phoenix application with installed Phoenix Dashboard the metrics can be used by defining a metric in the Telemetry module:\n\n```elixr\n      summary(\"mongodb_driver.execution.duration\",\n        tags: [:collection, :command],\n        unit: {:microsecond, :millisecond}\n      ),\n```\n\nThen you see for each collection the execution time for each different command in the Dashboard metric page.\n\n## Network compression\n\nThe driver supports two compressors\n\n* zlib, which is supported by Erlang \n* zstd, which is optional and supported by https://github.com/silviucpp/ezstd bindings.\n\nTo activate zlib compression:\n1. Append `compressors=zlib` to the URL connection string:\n```elixir\n{:ok, top} = Mongo.start_link(url: \"mongodb://localhost:27017/db?compressors=zlib\")\n```\n\nTo activate zstd compression:\n1. Add `{:ezstd, \"~\u003e 1.1\"}` to the dependencies of your `mix.exs` file. The driver will provide the related code.\n2. Append `compressors=zstd` to the URL connection string:\n\n```elixir\n{:ok, top} = Mongo.start_link(url: \"mongodb://localhost:27017/db?compressors=zstd\")\n```\n\nThe driver uses compression for the following functions:\n\n* `Mongo.aggregate/4`\n* `Mongo.find/4`\n* `Mongo.insert_one/4`\n* `Mongo.insert_many/4`\n* `Mongo.update/4`\n* `Mongo.update_documents/6`\n* `Mongo.find_one_and_update/5`\n* `Mongo.find_one_and_replace/5`\n* `Mongo.find_one_and_delete/4`\n* `Mongo.count/4`\n* `Mongo.distinct/5`\n* `Mongo.delete_documents/5`\n* `Mongo.create/4`\n\nYou can disable the compression for a single function by using the option `compression: false`, for example:\n\n```\nMongo.find(top, \"tasks\", %{}, compression: false) |\u003e Enum.to_list()\n```\nThe compression significantly reduces the amount of data, while increasing the load on the CPU.\nThis is certainly interesting for environments in which network transmission has to be paid for.\n\nzlib compression requires a greater penalty in terms of speed than zstd compression. \nThe zstd compression offers a good compromise between compression rate and speed and \nis undoubtedly supported by all current MongoDB.\n\nThe speed also depends on the `batch_size` attribute. A higher speed is achieved for certain batch sizes. \nSimple experiments can be carried out here to determine which size shortens the duration of the queries:\n\n```elixir\n:timer.tc(fn -\u003e Mongo.find(top, \"tasks\", %{}, limit: 30_000, batch_size: 1000) |\u003e Stream.reject(fn _x -\u003e true end) |\u003e Stream.run() end)\n```\n\n## Connection Pooling\n\nThe driver supports pooling by DBConnection (2.x). By default `mongodb_driver` will start a single\nconnection, but it also supports pooling with the `:pool_size` option. For 3 connections add the `pool_size: 3` option to `Mongo.start_link` and to all\nfunction calls in `Mongo` using the pool:\n\n```elixir\n# Starts an pooled connection\n{:ok, top} = Mongo.start_link(url: \"mongodb://localhost:27017/db-name\", pool_size: 3)\n\n# Gets an enumerable stream for the results\ntop\n|\u003e Mongo.find(\"test-collection\", %{})\n|\u003e Enum.to_list()\n```\n\nIf you're using pooling it is recommended to add it to your application supervisor:\n\n```elixir\ndef start(_type, _args) do\n\n  children = [\n    {Mongo, [name: :mongo_db, url: \"mongodb://localhost:27017/test\", pool_size: 3]}\n  ]\n\n  opts = [strategy: :one_for_one, name: MyApp.Supervisor]\n  Supervisor.start_link(children, opts)\nend\n```\n\nWe can use the `:mongo_db` atom instead of a process pid. This allows us to call the `Mongo` functions directly from\nevery place in the code.\n\n## Replica Sets\n\nBy default, the driver will discover the deployment's topology and will connect\nto the replica set automatically, using either the seed list syntax or the URI\nsyntax. Assuming the deployment has nodes at `hostname1.net:27017`,\n`hostname2.net:27017` and `hostname3.net:27017`, either of the following\ninvocations will discover the entire deployment:\n\n```elixir\n{:ok, pid} = Mongo.start_link(database: \"test\", seeds: [\"hostname1.net:27017\"])\n\n{:ok, pid} = Mongo.start_link(url: \"mongodb://hostname1.net:27017/test\")\n```\n\nTo ensure that the connection succeeds even when some of the nodes are not\navailable, it is recommended to list all nodes in both the seed list and the\nURI, as follows:\n\n```elixir\n{:ok, pid} = Mongo.start_link(database: \"test\", seeds: [\"hostname1.net:27017\", \"hostname2.net:27017\", \"hostname3.net:27017\"])\n\n{:ok, pid} = Mongo.start_link(url: \"mongodb://hostname1.net:27017,hostname2.net:27017,hostname3.net:27017/test\")\n```\n\nUsing an SRV URI also discovers all nodes of the deployment automatically.\n\n## Migration\n\nDespite the schema-free approach, migration is still desirable. Migrations are used to maintain the indexes \nand to drop collections that are no longer needed. Capped collections must be migrated. \nThe driver provides a workflow similar to Ecto that can be used to create migrations.\n\nFirst we create a migration script:\n```elixir\n\nmix mongo.gen.migration add_indexes\n\n```\n\nIn `priv/mongo/migrations` you will find an Elixir script like `20220322173354_add_indexes.exs`:\n\n```elixr\ndefmodule Mongo.Migrations.AddIndexes do\n  def up() do\n    indexes = [\n      [key: [email: 1], name: \"email_index\", unique: true]\n    ]\n\n    Mongo.create_indexes(:my_db, \"my_collection\", indexes)\n  end\n\n  def down() do\n    Mongo.drop_index(:my_db, \"my_collection\", \"email_index\")\n  end\nend\n\n```\n\nAfter that you can run the migration using a task:\n\n```\nmix mongo.migrate\n\n🔒 migrations locked\n⚡️ Successfully migrated Elixir.Mongo.Migrations.CreateIndex\n🔓 migrations unlocked\n\n```\n\nOr let it run if your application starts:\n\n```elixir\ndefmodule MyApp.Release do\n  @moduledoc \"\"\"\n  Used for executing DB release tasks when run in production without mix\n  installed.\n  \"\"\"\n\n  def migrate() do\n    Application.load(:my_app)\n    Application.ensure_all_started(:ssl)\n    Application.ensure_all_started(:mongodb_driver)\n    Mongo.start_link(name: :mongo_db, url: \"mongodb://localhost:27017/my-database\", timeout: 60_000, pool_size: 1, idle_interval: 10_000)\n\n    Mongo.Migration.migrate()\n  end\nend\n```\n\nWith the release features of Elixir you can add an overlay script like this:\n\n```shell\n#!/bin/sh\ncd -P -- \"$(dirname -- \"$0\")\"\nexec ./my_app eval MyApp.Release.migrate\n```\n\n```shell\n#!/bin/sh\ncd -P -- \"$(dirname -- \"$0\")\"\nPHX_SERVER=true exec ./my_app start\n```\n\nAnd then you need just to call migrate before you start the server:\n\n```shell\n/app/bin/migrate \u0026\u0026 /app/bin/server\n```\n\nOr if you use a Dockerfile:\n\n```dockerfile\nENTRYPOINT /app/bin/migrate \u0026\u0026 /app/bin/server\n```\n\nThe migration module tries to *lock* the migration collection to ensure that only one instance is running the migration. \nUnfortunately MongoDB does not support collection locks, so need to use a software lock:\n\n```elixir\nMongo.update_one(topology, \n  \"migrations\", \n  %{_id: \"lock\", used: false}, \n  %{\"$set\": %{used: true}}, \n  upsert: true)\n```\nYou can lock and unlock the migration collection using these functions in case of an error:\n\n1. `Mongo.Migration.lock()` \n2. `Mongo.Migration.unlock()` or `mix mongo.unlock`\n\nIf nothing helps, just delete the document with `{_id: \"lock\"}` from the migration collection.\n\nFor more information see:\n\n- `Mongo.Migration`\n- `Mix.Tasks.Mongo`\n- https://hexdocs.pm/mix/1.14/Mix.Tasks.Release.html\n\n### Configuration:\nYou need to configure the migration module and specify at least the `:otp_app` and `:topology` values. Here are the\ndefault values:\n\n    config :mongodb_driver,\n        migration:\n            [\n                topology: :mongo,\n                collection: \"migrations\",\n                path: \"migrations\",\n                otp_app: :mongodb_driver\n            ]\n\nThe following  options are available:\n* `:collection` - Version numbers of migrations will be saved in a collection named `migrations` by default.\n* `:path` - the `priv` directory for migrations. `:path` defaults to \"migrations\" and migrations should be placed at \"priv/mongo/migrations\". The pattern to build the path is `:priv/:topology/:path`\n* `:otp_app` - the name of the otp_app to resolve the `priv` folder, defaults to `:mongodb_driver`. In most cases you use your application name.\n* `:topology` - the topology for running the migrations, `:topology` defaults to `:mongo`\n\n### Supporting multiple topologies:\n\nEach function `lock/1, unlock/1, migrate/1, drop/1` accepts a keyword list (options) to override the default config having \nfull control of the migration process. The options are passed through the migration scripts. \n\nThat means you can support multiple topologies, databases and migration collections. Example\n\n    Mongo.start_link(name: :topology_1, url: \"mongodb://localhost:27017/mig_test_1\", timeout: 60_000, pool_size: 5, idle_interval: 10_000)\n    Mongo.start_link(name: :topology_2, url: \"mongodb://localhost:27017/mig_test_2\", timeout: 60_000, pool_size: 5, idle_interval: 10_000)\n\n    IO.puts(\"running default migration\")\n    Mongo.Migration.migrate() ## default values specified in the configs\n\n    IO.puts(\"running topology_2 migration\")\n    Mongo.Migration.migrate([topology: :topology_2]) ## override the topology \n\nAdding the options parameter in the `up/1` and `down/1` function of the migration script is supported as well. It is\npossible to pass additional parameters to the migration scripts.\n\n    defmodule Mongo.Migrations.Topology.CreateIndex do\n        def up(opts) do \n            IO.inspect(opts)\n            ...\n        end\n        \n        def down(opts) do\n            IO.inspect(opts)\n            ...\n        end\n    end\n\nThe topology is part of the namespace and of the migration path as well. The default value is defined in the configuration.\nYou can specify the topology in the case of creating a new migration script by appending the name to the script call:\n```elixir\n\nmix mongo.gen.migration add_indexes topology_2\n\n```\n\nIn `priv/topology_2/migrations` you will find an Elixir script like `20220322173354_add_indexes.exs`:\n\n```elixr\ndefmodule Mongo.Migrations.Topology2.AddIndexes do\n    ...\nend\n\n```\n\nBy using the `:topology` keyword, you can organise the migration scripts in different sub-folders. The migration path is prefixed with the `priv` folder of the application and the topology name.\n\nIf you call\n\n    Mongo.Migration.migrate([topology: :topology_2])\n\nthen the migration scripts under `/priv/topology_2/` are used and the options keyword list is passed through\nto the `up/1` function if it is implemented. That means you can create migration scripts for multiple topologies\nseparated in sub folders and module namespaces.\n\n## Auth Mechanisms\n\nFor versions of Mongo 3.0 and greater, the auth mechanism defaults to SCRAM.\n\nIf connecting to MongoDB Enterprise Edition or MongoDB Atlas, the [PLAIN](https://www.mongodb.com/docs/manual/tutorial/authenticate-nativeldap-activedirectory/)\nauth mechanism is supported for LDAP authentication. The GSSAPI auth mechanism used for Kerberos authentication\nis not currently supported.\n\nIf you'd like to use [MONGODB-X509](https://www.mongodb.com/docs/v6.0/tutorial/configure-x509-client-authentication/)\nauthentication, you can specify that as a `start_link` option. \n\nYou need roughly three additional configuration steps:\n\n* Deploy with x.509 Authentication\n* Add x.509 Certificate subject as a User\n* Authenticate with an x.509 Certificate\n\nTo get the x.509 authentication working you need to prepare the ssl configuration accordingly:\n* you need to set the ssl option: `verify_peer`\n* you need to specify the `cacertfile` because Erlang BEAM don't provide any CA certificate store by default\n* you maybe need to customize the hostname check to allow wildcard certificates\n* you need to specify the `username` from the subject entry of the user certificate\n\nIf you use a user certificate from Atlas a working configuration looks like this. First we\nuse the [castore](https://hex.pm/packages/castore) package as the CA certificate store. After downloading\nthe user certificate we extract the username subject entry from the PEM file:\n\n```shell\nopenssl x509 -in \u003cpathToClientPEM\u003e -inform PEM -subject -nameopt RFC2253\n\n\u003e CN=cert-user\n```\n\nThe configuration looks now:\n```elixir\n  opts = [\n      url: \"mongodb+srv://cluster0.xxx.mongodb.net/myFirstDatabase?authSource=%24external\u0026retryWrites=true\u0026w=majority\",\n      ssl: true,\n      username: \"CN=cert-user\",\n      password: \"\",\n      auth_mechanism: :x509,\n      ssl_opts: [\n        verify: :verify_peer,\n        cacertfile: to_charlist(CAStore.file_path()),\n        certfile: '/path-to-cert/X509-cert-2227052404946303101.pem',\n        customize_hostname_check: [\n          match_fun:\n            :public_key.pkix_verify_hostname_match_fun(:https)\n        ]\n      ]]\n\n    Mongo.start_link(opts)\n```\n\nCurrently, we need to specify *an empty password* to get the x.509 auth module working. This will be changed soon.  \n\n## x509 and using a dedicated MongoDB Atlas server\n\nUsing OTP 26 changed the default configuration regarding TLS. You may see issues when \nconnecting to a dedicated Atlas Server using OTP 26. You can restrict the allowed versions and force to use TLS 1.2 instead\nof TLS 1.3.\n\n```elixir\n   ...\n    versions: [:\"tlsv1.2\"],\n   ...\n```\n\nSee also [MongoDB Security](https://www.mongodb.com/docs/atlas/reference/faq/security/) and \nthe [Issue 226](https://github.com/zookzook/elixir-mongodb-driver/issues/226) for some background information.\n\n## AWS, TLS and Erlang SSL Ciphers\n\nSome MongoDB cloud providers (notably AWS) require a particular TLS cipher that isn't enabled\nby default in the Erlang SSL module. In order to connect to these services,\nyou'll want to add this cipher to your `ssl_opts`:\n\n```elixir\n{:ok, pid} = Mongo.start_link(database: \"test\",\n      ssl_opts: [\n        ciphers: ['AES256-GCM-SHA384'],\n        cacertfile: \"...\",\n        certfile: \"...\")\n      ]\n)\n```\n\nSee the example `AWSX509.Example` as well.\n\n## Timeout\n\nThe `:timeout` option sets the maximum time that the caller is allowed to hold the connection’s state (to send and to receive data). \nThe default value is 15 seconds. The connection pool defines additional timeout values. \nYou can use the `:timeout` as a global option to override the default value:\n\n```elixir\n# Starts an pooled connection\n{:ok, top} = Mongo.start_link(url: \"mongodb://localhost:27017/db-name\", timeout: 60_000)\n```\n\nEach single connection uses `60_000` (60 seconds) as the timeout value instead of `15_000`. But you can override the default value by\nusing the `:timeout` option, when running a single command:\n\n```elixir\nMongo.find(top, \"dogs\", %{}, timeout: 120_000)\n```\n\nNow the driver will use 120 seconds as the timeout for the single query.\n\n## Read Preferences\n\nThe `:read_preference` option sets [read preference](https://www.mongodb.com/docs/manual/core/read-preference/) for the query. The read preference is\na simple map, supporting the following keys:\n\n* `:mode`, possible values: `:primary`, `:primary_preferred`, `:secondary`, `:secondary_preferred` and `:nearest`\n* `:max_staleness_ms`, the maxStaleness value in milliseconds\n* `:tags`, the set of tags, for example: `[dc: \"west\", usage: \"production\"]`\n\nThe driver selects the server using the read preference. \n\n```elixr \nprefs = %{\n    mode: :secondary,\n    max_staleness_ms: 120_000,\n    tags: [dc: \"west\", usage: \"production\"]\n}\n\nMongo.find_one(top, \"dogs\", %{name: \"Oskar\"}, read_preference: prefs)\n```\n\n## Change Streams\n\nChange streams are available in replica set and sharded cluster deployments\nand tell you about changes of documents in collections. They work like endless\ncursors.\n\nThe special thing about change streams is that they are resumable: in case of\na resumable error, no exception is propagated to the application, but instead\nthe cursor is re-scheduled at the last successful location.\n\nThe following example will never stop, thus it is a good idea to use a process\nfor reading from change streams:\n\n```elixir\nseeds = [\"hostname1.net:27017\", \"hostname2.net:27017\", \"hostname3.net:27017\"]\n{:ok, top} = Mongo.start_link(database: \"my-db\", seeds: seeds, appname: \"getting rich\")\nstream =  Mongo.watch_collection(top, \"accounts\", [], fn doc -\u003e IO.puts \"New Token #{inspect doc}\" end, max_time: 2_000 )\nEnum.each(stream, fn doc -\u003e IO.puts inspect doc end)\n```\n\nAn example with a spawned process that sends messages to the monitor process:\n\n```elixir\ndef for_ever(top, monitor) do\n    stream = Mongo.watch_collection(top, \"users\", [], fn doc -\u003e send(monitor, {:token, doc}) end)\n    Enum.each(stream, fn doc -\u003e send(monitor, {:change, doc}) end)\nend\n\nspawn(fn -\u003e for_ever(top, self()) end)\n```\n\nFor more information see `Mongo.watch_collection/5`\n\n## Indexes\n\nTo create indexes you can call the function `Mongo.create_indexes/4`:\n\n```elixir\nindexes =  [[key: [files_id: 1, n: 1], name: \"files_n_index\", unique: true]]\nMongo.create_indexes(top, \"my_collection\", indexes, opts)\n```\n\nYou specify the `indexes` parameter as a keyword list with all options described in the documentation of the [createIndex](https://docs.mongodb.com/manual/reference/command/createIndexes/#dbcmd.createIndexes) command.\n\nFor more information see:\n\n- `Mongo.create_indexes/4`\n- `Mongo.drop_index/4`\n\n## Bulk Writes\n\nThe motivation for bulk writes lies in the possibility of optimization, the same operations\nto group. Here, a distinction is made between disordered and ordered bulk writes.\nIn disordered, inserts, updates, and deletes are grouped as individual commands\nsent to the database. There is no influence on the order of the execution.\nA good use case is the import of records from one CSV file.\nThe order of the inserts does not matter.\n\nFor ordered bulk writers, order compliance is important to keep.\nIn this case, only the same consecutive operations are grouped.\n\nCurrently, all bulk writes are optimized in memory. This is unfavorable for large bulk writes.\nIn this case, one can use streaming bulk writes that only have a certain set of\ngroup operation in memory and when the maximum number of operations\nhas been reached, operations are written to the database. The size can be specified.\n\nUsing ordered bulk writes. In this example we first insert some dog's name, add an attribute `kind`\nand change all dogs to cats. After that we delete three cats. This example would not work with\nunordered bulk writes.\n\n```elixir\n\nbulk = \"bulk\"\n       |\u003e OrderedBulk.new()\n       |\u003e OrderedBulk.insert_one(%{name: \"Greta\"})\n       |\u003e OrderedBulk.insert_one(%{name: \"Tom\"})\n       |\u003e OrderedBulk.insert_one(%{name: \"Waldo\"})\n       |\u003e OrderedBulk.update_one(%{name: \"Greta\"}, %{\"$set\": %{kind: \"dog\"}})\n       |\u003e OrderedBulk.update_one(%{name: \"Tom\"}, %{\"$set\": %{kind: \"dog\"}})\n       |\u003e OrderedBulk.update_one(%{name: \"Waldo\"}, %{\"$set\": %{kind: \"dog\"}})\n       |\u003e OrderedBulk.update_many(%{kind: \"dog\"}, %{\"$set\": %{kind: \"cat\"}})\n       |\u003e OrderedBulk.delete_one(%{kind: \"cat\"})\n       |\u003e OrderedBulk.delete_one(%{kind: \"cat\"})\n       |\u003e OrderedBulk.delete_one(%{kind: \"cat\"})\n\nresult = Mongo.BulkWrite.write(top, bulk, w: 1)\n```\n\nIn the following example we import 1.000.000 integers into the MongoDB using the stream api:\n\nWe need to create an insert operation for each number. Then we call the `Mongo.UnorderedBulk.stream`\nfunction to import it. This function returns a stream function that accumulates\nall inserts operations until the limit `1000` is reached. In this case the operation group is send to\nMongoDB. So using the stream api you can reduce the memory using while\nimporting big volume of data.\n\n```elixir\n1..1_000_000\n|\u003e Stream.map(fn i -\u003e Mongo.BulkOps.get_insert_one(%{number: i}) end)\n|\u003e Mongo.UnorderedBulk.write(:mongo, \"bulk\", 1_000)\n|\u003e Stream.run()\n```\n\nFor more information see:\n\n- `Mongo.UnorderedBulk`\n- `Mongo.OrderedBulk`\n- `Mongo.BulkWrite`\n- `Mongo.BulkOps`\n\nand have a look at the test units as well.\n\n## GridFS\n\nThe driver supports the GridFS specifications. You create a `Mongo.GridFs.Bucket`\nstruct and with this struct you can upload and download files. For example:\n\n```elixir\n    bucket = Bucket.new(top)\n    upload_stream = Upload.open_upload_stream(bucket, \"test.jpg\")\n    src_filename = \"./test/data/test.jpg\"\n    File.stream!(src_filename, [], 512) |\u003e Stream.into(upload_stream) |\u003e Stream.run()\n\n    file_id = upload_stream.id\n```\n\nIn the example a new bucket with default values is used to upload a file from the file system (`./test/data/test.jpg`) to the MongoDB (using the name `test.jpg`). The `upload_stream` struct contains the id of the new file which can be used to download the stored file. The following code fragments downloads the file by using the `file_id`.\n\n```elixir\n    dest_filename = \"/tmp/my-test-file.jps\"\n\n    with {:ok, stream} \u003c- Mongo.GridFs.Download.open_download_stream(bucket, file_id) do\n      stream\n      |\u003e Stream.into(File.stream!(dest_filename))\n      |\u003e Stream.run\n    end\n```\n\nFor more information see:\n\n- [Mongo.GridFs.Bucket](https://hexdocs.pm/mongodb_driver/Mongo.GridFs.Bucket.html#content)\n- [Mongo.GridFs.Download](https://hexdocs.pm/mongodb_driver/Mongo.GridFs.Download.html#content)\n- [Mongo.GridFs.Upload](https://hexdocs.pm/mongodb_driver/Mongo.GridFs.Upload.html#content)\n\n## Transactions\n\nSince MongoDB 4.x, transactions for multiple write operations are possible. Transaction uses sessions, which\njust contain a transaction number for each transaction. The `Mongo.Session` is responsible for the\ndetails, and you can use a convenient api for transactions:\n\n```elixir\n\n{:ok, ids} = Mongo.transaction(top, fn -\u003e\n{:ok, %InsertOneResult{:inserted_id =\u003e id1}} = Mongo.insert_one(top, \"dogs\", %{name: \"Greta\"})\n{:ok, %InsertOneResult{:inserted_id =\u003e id2}} = Mongo.insert_one(top, \"dogs\", %{name: \"Waldo\"})\n{:ok, %InsertOneResult{:inserted_id =\u003e id3}} = Mongo.insert_one(top, \"dogs\", %{name: \"Tom\"})\n{:ok, [id1, id2, id3]}\nend, w: 1)\n\n```\nThe `Mongo.transaction/3` function supports nesting. This allows the functions to be called from each other and all write operations\nare still in the same transaction. The session is stored in the process dictionary under the key `:session`. The surrounding\n`Mongo.transaction/3` call creates the session and starts the transaction, storing the session in the process dictionary, commits or\naborts the transaction. All other `Mongo.transaction/3` calls just call the function parameter without other actions.\n\n```elixir\ndef insert_dog(top, name) do\n  Mongo.insert_one(top, \"dogs\", %{name: name})\nend\n\ndef insert_dogs(top) do\n  Mongo.transaction(top, fn -\u003e\n    insert_dog(top, \"Tom\")\n    insert_dog(top, \"Bell\")\n    insert_dog(top, \"Fass\")\n    :ok\n  end)\nend\n\n:ok = Mongo.transaction(top, fn -\u003e\n    insert_dog(top, \"Greta\")\n    insert_dogs(top)\nend)\n```\n\nIt is also possible to get more control over the progress of the transaction:\n\n```elixir\nalias Mongo.Session\n\n{:ok, session} = Session.start_session(top, :write, [])\n:ok = Session.start_transaction(session)\n\nMongo.insert_one(top, \"dogs\", %{name: \"Greta\"}, session: session)\nMongo.insert_one(top, \"dogs\", %{name: \"Waldo\"}, session: session)\nMongo.insert_one(top, \"dogs\", %{name: \"Tom\"}, session: session)\n\n:ok = Session.commit_transaction(session)\n:ok = Session.end_session(top, session)\n```\nFor more information see `Mongo.Session` and have a look at the test units as well.\n\n### Aborting a transaction\n\nYou have some options to abort a transaction. The simplest possibility is to return an `:error`. For nested\nfunction calls, the `Mongo.abort_transaction/1` function call that throws an exception is suitable.\nThat means, you can just generate a `raise :should_not_happen` exception as well.\n\n## Command Monitoring\n\nYou can watch all events that are triggered while the driver sends requests and processes responses. You can use the\n`Mongo.EventHandler` as a starting point. It logs the events from the topic `:commands` (by ignoring the `:isMaster` command)\nto `Logger.info`:\n\n```elixir\niex\u003e Mongo.EventHandler.start()\niex\u003e {:ok, top} = Mongo.start_link(url: \"mongodb://localhost:27017/test\")\n{:ok, #PID\u003c0.226.0\u003e}\n iex\u003e Mongo.find_one(top, \"test\", %{}) |\u003e Enum.to_list()\n[info] Received command: %Mongo.Events.CommandStartedEvent{command: [find: \"test\", ...\n[info] Received command: %Mongo.Events.CommandSucceededEvent{command_name: :find, ...\n```\n\n## Testing\n\nLatest MongoDB is used while running the tests. Replica set of three nodes is created and runs all tests, except the socket and ssl test. If you want to\nrun the test cases against other MongoDB deployments or older versions, you can use the [mtools](https://github.com/rueckstiess/mtools) for deployment and run the test cases locally:\n\n```bash\npyenv global 3.6\npip3 install --upgrade pip\npip3 install 'mtools[all]'\nexport PATH=to-your-mongodb/bin/:$PATH\nulimit -S -n 2048 ## in case of Mac OS X\nmlaunch init --setParameter enableTestCommands=1 --replicaset --name \"rs_1\"\nmongosh --host localhost:27017 --eval 'rs.initiate({_id: \"rs_1\", members: [{_id: 0, host: \"127.0.0.1:27017\"}, {_id: 1, host: \"127.0.0.1:27018\"}, {_id: 2, host: \"127.0.0.1:27019\"}]})'\nmix test --exclude ssl --exclude socket\n```\n\nThe SSL test suite is disabled by default.\n\n### Enable the SSL Tests\n\n`mix test --exclude ssl`\n\n### Enable SSL on Your MongoDB Server\n\n```bash\n$ openssl req -newkey rsa:2048 -new -x509 -days 365 -nodes -out mongodb-cert.crt -keyout mongodb-cert.key\n$ cat mongodb-cert.key mongodb-cert.crt \u003e mongodb.pem\n$ mongod --sslMode allowSSL --sslPEMKeyFile /path/to/mongodb.pem\n```\n\n- For `--sslMode` you can use one of `allowSSL` or `preferSSL`\n- You can enable any other options you want when starting `mongod`\n\n## Additional articles\n* [Connecting to MongoDB with Elixir](https://zookzook.github.io/2024/08-25.html)\n* [Using Network Compression with MongoDB in Elixir](https://zookzook.github.io/2024/09-15.html)\n\n## Copyright and License\n\nCopyright 2015 Eric Meadows-Jönsson and Justin Wood \\\nCopyright 2019 - present Michael Maier\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzookzook%2Felixir-mongodb-driver","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzookzook%2Felixir-mongodb-driver","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzookzook%2Felixir-mongodb-driver/lists"}