Skip to content

Commit 7583705

Browse files
committed
added more documentation
1 parent 3024046 commit 7583705

File tree

2 files changed

+108
-34
lines changed

2 files changed

+108
-34
lines changed

lib/bson.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ defmodule BSON do
55

66
@type t :: document | String.t | atom | number | boolean | BSON.Binary.t |
77
BSON.ObjectId.t | BSON.Regex.t |
8-
BSON.JavaScript.t | BSON.Timestamp.t | [t]
8+
BSON.JavaScript.t | BSON.Timestamp.t | BSON.LongNumber.t | [t]
99
@type document :: %{atom => BSON.t} | %{String.t => BSON.t} |
1010
[{atom, BSON.t}] | [{String.t, BSON.t}]
1111

lib/mongo/session.ex

Lines changed: 107 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ defmodule Mongo.Session do
22

33
@moduledoc """
44
This module implements the details of the transactions api ([see specs](https://github.com/mongodb/specifications/blob/master/source/transactions/transactions.rst#committransaction)).
5-
It uses the `:gen_statem` behaviour ([A nice tutorial](https://andrealeopardi.com/posts/connection-managers-with-gen_statem/)) to manage the different states.
5+
It uses the `:gen_statem` behaviour ([a nice introduction](https://andrealeopardi.com/posts/connection-managers-with-gen_statem/)) to manage the different states.
66
7-
In case of MongoDB 3.6 or greater the driver uses sessions for each operation. If no session is created the driver will create a so-called implict session. A session is a UUID-Number which
8-
is added to some operations. The sessions are used to manage the transaction state as well. In most situation you need not to create a session instance, so the interface of the driver is not changed.
7+
In case of MongoDB 3.6 or greater the driver uses sessions for each operation. If no session is created the driver will create a so-called implicit session. A session is a UUID-Number which
8+
is added to some operations. The sessions are used to manage the transaction state as well. In most situation you need not to create a session instance, so the api of the driver is not changed.
99
1010
In case of multiple insert statemantes you can use transaction (MongoDB 4.x) to be sure that all operations are grouped like a single operation. Prerequisites for transactions are:
1111
MongoDB 4.x must be used as replica set or cluster deployment. The collection used in the operations must already exist. Some operation are not allowed (For example: create index or call count).
@@ -24,7 +24,7 @@ defmodule Mongo.Session do
2424
:ok = Session.commit_transaction(session)
2525
:ok = Session.end_session(top, session)
2626
27-
First you start a explicit session and a transactions. Use need to use the session for each insert statement as an options with key `:session` otherwise the insert statement won't be
27+
First you start a explicit session and a transactions. Use the session for each insert statement as an options with key `:session` otherwise the insert statement won't be
2828
executed in the transaction. After that you commit the transaction and end the session by calling `end_session`.
2929
3030
## Convenient API for Transactions
@@ -42,7 +42,59 @@ defmodule Mongo.Session do
4242
{:ok, [id1, id2, id3]}
4343
end, w: 1)
4444
45-
If the callback is successfull then it returns a tupel with the keyword `:ok` and a used defined result like `{:ok, [id1, id2, id3]}`
45+
If the callback is successfull then it returns a tupel with the keyword `:ok` and a used defined result like `{:ok, [id1, id2, id3]}`. In this example we use
46+
the write concern `w: 1`. The write concern used in the insert operation will be removed by the driver. It is applied in the commit transaction command.
47+
48+
## Implicit vs explicit sessions
49+
50+
In most cases the driver will create implicit sessions for you. Each time when you run a query or a command the driver
51+
executes the following functions:
52+
53+
with {:ok, session} <- Session.start_implicit_session(topology_pid, type, opts),
54+
result <- exec_command_session(session, new_cmd, opts),
55+
:ok <- Session.end_implict_session(topology_pid, session) do
56+
...
57+
58+
This behaviour is specified by the mongodb specification for [drivers](https://github.com/mongodb/specifications/blob/master/source/sessions/driver-sessions.rst#explicit-vs-implicit-sessions).
59+
60+
If you use the `:causal_consistency` flag, then you need to create an explicit session:
61+
62+
alias Mongo.Session
63+
64+
{:ok, session} = Session.start_session(top, :write, causal_consistency: true)
65+
66+
Mongo.delete_many(top, "dogs", %{"Greta"}, session: session)
67+
{:ok, 0} = Mongo.count(top, "dogs", %{name: "Greta"}, session: session)
68+
69+
:ok = Session.end_session(top, session)
70+
71+
For more information about causal consistency see the [officially documentation](https://docs.mongodb.com/manual/core/read-isolation-consistency-recency/#causal-consistency).
72+
73+
If you want to use transaction, then you need to create a session as well:
74+
75+
alias Mongo.Session
76+
77+
{:ok, session} = Session.start_session(top, :write, [])
78+
:ok = Session.start_transaction(session)
79+
80+
Mongo.insert_one(top, "dogs", %{name: "Greta"}, session: session)
81+
Mongo.insert_one(top, "dogs", %{name: "Waldo"}, session: session)
82+
Mongo.insert_one(top, "dogs", %{name: "Tom"}, session: session)
83+
84+
:ok = Session.commit_transaction(session)
85+
:ok = Session.end_session(top, session)
86+
87+
You can shorten this code by using the `with_transaction` function:
88+
89+
alias Mongo.Session
90+
91+
{:ok, ids} = Session.with_transaction(top, fn opts ->
92+
{:ok, %InsertOneResult{:inserted_id => id1}} = Mongo.insert_one(top, "dogs", %{name: "Greta"}, opts)
93+
{:ok, %InsertOneResult{:inserted_id => id2}} = Mongo.insert_one(top, "dogs", %{name: "Waldo"}, opts)
94+
{:ok, %InsertOneResult{:inserted_id => id3}} = Mongo.insert_one(top, "dogs", %{name: "Tom"}, opts)
95+
{:ok, [id1, id2, id3]}
96+
end, w: 1)
97+
4698
"""
4799

48100
@behaviour :gen_statem
@@ -101,18 +153,11 @@ defmodule Mongo.Session do
101153
end
102154
end
103155

104-
@doc """
105-
Start a new transation.
106-
"""
107-
@spec start_transaction(Session.t) :: :ok | {:error, term()}
108-
def start_transaction(pid) do
109-
:gen_statem.call(pid, {:start_transaction})
110-
end
111-
112156
@doc """
113157
Start a new implicit session only if no explicit session exists. It returns the session in the `opts` keyword list or
114158
creates a new one.
115159
"""
160+
@spec start_implicit_session(GenServer.server, atom, keyword()) :: {:ok, Session.t} | {:error, term()}
116161
def start_implicit_session(topology_pid, type, opts) do
117162
case Keyword.get(opts, :session, nil) do
118163
nil ->
@@ -128,22 +173,33 @@ defmodule Mongo.Session do
128173
end
129174

130175
@doc """
131-
Commit the current transation
176+
Start a new transation.
177+
"""
178+
@spec start_transaction(Session.t) :: :ok | {:error, term()}
179+
def start_transaction(pid) do
180+
:gen_statem.call(pid, {:start_transaction})
181+
end
182+
183+
@doc """
184+
Commit the current transation.
132185
"""
186+
@spec commit_transaction(Session.t) :: :ok | {:error, term()}
133187
def commit_transaction(pid) do
134188
:gen_statem.call(pid, {:commit_transaction})
135189
end
136190

137191
@doc """
138-
Abort the current transation and rollback all updates.
192+
Abort the current transation and rollback all changes.
139193
"""
194+
@spec abort_transaction(Session.t) :: :ok | {:error, term()}
140195
def abort_transaction(pid) do
141196
:gen_statem.call(pid, {:abort_transaction})
142197
end
143198

144199
@doc """
145-
Merge the session / transaction data into the cmd.
200+
Merge the session / transaction data into the cmd. There is no need to call this function directly. It is called automatically.
146201
"""
202+
@spec bind_session(Session.t, BSON.document) :: :ok | {:error, term()}
147203
def bind_session(nil, cmd) do
148204
cmd
149205
end
@@ -152,8 +208,10 @@ defmodule Mongo.Session do
152208
end
153209

154210
@doc """
155-
Update the `operationTime` for causally consistent read commands
211+
Update the `operationTime` for causally consistent read commands. There is no need to call this function directly. It is called automatically.
156212
"""
213+
@spec update_session(Session.t, %{key: BSON.Timestamp.t}, keyword()) :: BSON.document
214+
def update_session(pid, doc, opts \\ [])
157215
def update_session(pid, %{"operationTime" => operationTime} = doc, opts) do
158216
case opts |> write_concern() |> acknowledged?() do
159217
true -> advance_operation_time(pid, operationTime)
@@ -168,13 +226,15 @@ defmodule Mongo.Session do
168226
@doc """
169227
Advance the `operationTime` for causally consistent read commands
170228
"""
229+
@spec advance_operation_time(Session.t, BSON.Timestamp.t) :: none()
171230
def advance_operation_time(pid, timestamp) do
172231
:gen_statem.cast(pid, {:advance_operation_time, timestamp})
173232
end
174233

175234
@doc """
176-
End implicit session
235+
End implicit session. There is no need to call this function directly. It is called automatically.
177236
"""
237+
@spec end_implict_session(GenServer.server, Session.t) :: :ok | :error
178238
def end_implict_session(topology_pid, session) do
179239
with {:ok, session_server} <- :gen_statem.call(session, {:end_implicit_session}) do
180240
Topology.checkin_session(topology_pid, session_server)
@@ -185,17 +245,30 @@ defmodule Mongo.Session do
185245
end
186246

187247
@doc """
188-
End explicit session
248+
End explicit session.
189249
"""
250+
@spec end_session(GenServer.server, Session.t) :: :ok | :error
190251
def end_session(topology_pid, session) do
191252
with {:ok, session_server} <- :gen_statem.call(session, {:end_session}) do
192253
Topology.checkin_session(topology_pid, session_server)
193254
end
194255
end
195256

196257
@doc """
197-
Convient function for running multiple write commands in a transaction
258+
Convenient function for running multiple write commands in a transaction.
259+
260+
## Example
261+
alias Mongo.Session
262+
263+
{:ok, ids} = Session.with_transaction(top, fn opts ->
264+
{:ok, %InsertOneResult{:inserted_id => id1}} = Mongo.insert_one(top, "dogs", %{name: "Greta"}, opts)
265+
{:ok, %InsertOneResult{:inserted_id => id2}} = Mongo.insert_one(top, "dogs", %{name: "Waldo"}, opts)
266+
{:ok, %InsertOneResult{:inserted_id => id3}} = Mongo.insert_one(top, "dogs", %{name: "Tom"}, opts)
267+
{:ok, [id1, id2, id3]}
268+
end, w: 1)
269+
198270
"""
271+
@spec with_transaction(Session.t, (keyword() -> {:ok, any()} | :error)) :: {:ok, any()} | :error | {:error, term}
199272
def with_transaction(topology_pid, fun, opts \\ []) do
200273

201274
with {:ok, session} <- Session.start_session(topology_pid, :write, opts),
@@ -232,26 +305,30 @@ defmodule Mongo.Session do
232305
end
233306

234307
@doc """
235-
Return the connection used in the session
308+
Return the connection used in the session.
236309
"""
310+
@spec connection(Session.t) :: pid
237311
def connection(pid) do
238312
:gen_statem.call(pid, {:connection})
239313
end
240314

241315
@doc """
242-
Return the server session used in the session
316+
Return the server session used in the session.
243317
"""
318+
@spec server_session(Session.t) :: ServerSession.t
244319
def server_session(pid) do
245320
:gen_statem.call(pid, {:server_session})
246321
end
247322

248323
@doc"""
249-
Check if the session is alive
324+
Check if the session is alive.
250325
"""
326+
@spec server_session(Session.t) :: boolean()
251327
def alive?(nil), do: false
252328
def alive?(pid), do: Process.alive?(pid)
253329

254330
@impl true
331+
# Initialization of the state machine
255332
def init({conn, server_session, type, wire_version, opts}) do
256333
data = %Session{conn: conn,
257334
server_session: server_session,
@@ -263,6 +340,7 @@ defmodule Mongo.Session do
263340
end
264341

265342
@impl true
343+
# start the transaction
266344
def handle_event({:call, from},
267345
{:start_transaction},
268346
transaction,
@@ -345,7 +423,6 @@ defmodule Mongo.Session do
345423
def handle_event({:call, from}, {:end_implicit_session}, _state, %Session{implicit: false}) do
346424
{:keep_state_and_data, {:reply, from, :noop}}
347425
end
348-
349426
def handle_event({:call, from}, {:server_session}, _state, %Session{server_session: session_server, implicit: implicit}) do
350427
{:keep_state_and_data, {:reply, from, {:ok, session_server, implicit}}}
351428
end
@@ -368,6 +445,9 @@ defmodule Mongo.Session do
368445
Logger.debug("Terminating because of #{inspect reason}")
369446
end
370447

448+
##
449+
# Run the commit transaction command.
450+
#
371451
defp run_commit_command(%{conn: conn, server_session: %ServerSession{session_id: id, txn_num: txn_num}, opts: opts}) do
372452

373453
Logger.debug("Running commit transaction")
@@ -387,12 +467,12 @@ defmodule Mongo.Session do
387467

388468
_doc = Mongo.exec_command(conn, cmd, database: "admin")
389469

390-
# {:ok, %{"$clusterTime" => %{"clusterTime" => #BSON.Timestamp<1567853627:8>,
391-
# "signature" => %{"hash" => #BSON.Binary<0000000000000000000000000000000000000000>, "keyId" => 0}},
392-
# "ok" => 1.0, "operationTime" => #BSON.Timestamp<1567853627:6>}}
393470
:ok
394471
end
395472

473+
##
474+
# Run the abort transaction command.
475+
#
396476
defp run_abort_command(%{conn: conn, server_session: %ServerSession{session_id: id, txn_num: txn_num}, opts: opts}) do
397477

398478
Logger.debug("Running abort transaction")
@@ -407,12 +487,6 @@ defmodule Mongo.Session do
407487

408488
_doc = Mongo.exec_command(conn, cmd, database: "admin")
409489

410-
#
411-
# doc:
412-
# %{"$clusterTime" => %{"clusterTime" => #BSON.Timestamp<1567853164:4>,
413-
# "signature" => %{"hash" => #BSON.Binary<0000000000000000000000000000000000000000>, "keyId" => 0}},
414-
#"ok" => 1.0, "operationTime" => #BSON.Timestamp<1567853164:4>}
415-
416490
:ok
417491
end
418492

0 commit comments

Comments
 (0)