-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathreplication.ex
More file actions
113 lines (90 loc) · 3.28 KB
/
Copy pathreplication.ex
File metadata and controls
113 lines (90 loc) · 3.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
defmodule Wal.Replication do
use Postgrex.ReplicationConnection
require Logger
def start_link(_opts) do
config = Wal.Repo.config()
# Automatically reconnect if we lose connection.
extra_opts = [
auto_reconnect: true
]
Postgrex.ReplicationConnection.start_link(__MODULE__, :ok, extra_opts ++ config)
end
@impl Postgrex.ReplicationConnection
def init(:ok) do
{:ok, %{step: :disconnected, messages: [], relations: %{}}}
end
@impl Postgrex.ReplicationConnection
def handle_connect(state) do
query =
"START_REPLICATION SLOT postgrex LOGICAL 0/0 (proto_version '1', publication_names 'postgrex_publication')"
Logger.debug(query)
{:stream, query, [], %{state | step: :streaming}}
end
@impl Postgrex.ReplicationConnection
# Primary Keep Alive Message
# https://www.postgresql.org/docs/current/protocol-replication.html#PROTOCOL-REPLICATION-PRIMARY-KEEPALIVE-MESSAGE
def handle_data(<<?k, wal_end::64, _server_time::64, should_reply::8>>, state) do
messages =
case should_reply do
# Standby Status Update
# https://www.postgresql.org/docs/current/protocol-replication.html#PROTOCOL-REPLICATION-STANDBY-STATUS-UPDATE
1 -> [<<?r, wal_end + 1::64, wal_end + 1::64, wal_end + 1::64, current_time()::64, 0>>]
0 -> []
end
Logger.debug("Responding to keep alive: #{should_reply} - #{inspect(messages)}")
{:noreply, messages, state}
end
# XLogData
# https://www.postgresql.org/docs/current/protocol-replication.html#PROTOCOL-REPLICATION-STANDBY-STATUS-UPDATE
def handle_data(<<?w, raw_lsn::64, _latest_lsn::64, _server_time::64, payload::bytes>>, state) do
payload = Wal.Decoder.parse(payload)
message = %{
lsn: raw_lsn,
type: payload.type,
payload: payload
}
state = handle_message(message, state)
{:noreply, state}
end
# When a RELATION message arrives, store its structure in memory.
defp handle_message(
%{type: :relation, payload: %{relation_id: relation_id} = relation} = _message,
%{relations: relations} = state
) do
%{state | relations: Map.put(relations, relation_id, relation)}
end
# When a COMMIT message arrives, apply the changes.
defp handle_message(
%{type: :commit} = _message,
%{messages: messages, relations: relations} = state
) do
changes =
Enum.reduce(messages, [], fn message, acc ->
if message.type in [:insert, :update, :delete] do
relation = Map.fetch!(relations, message.payload.relation_id)
data =
relation.columns
|> Enum.zip(message.payload.data)
|> Map.new(fn {%{name: name}, %{value: value}} ->
{name, value}
end)
change = %{
lsn: message.lsn,
type: message.type,
table: relation.relation_name,
data: data
}
[change | acc]
else
acc
end
end)
IO.inspect(changes, label: "Changes")
%{state | messages: []}
end
defp handle_message(message, %{messages: messages} = state) do
%{state | messages: [message | messages]}
end
@epoch DateTime.to_unix(~U[2000-01-01 00:00:00Z], :microsecond)
defp current_time, do: System.os_time(:microsecond) - @epoch
end