# `Electric.Replication.Changes`
[🔗](https://github.com/electric-sql/electric/tree/%40core/sync-service%401.7.3/packages/sync-service/lib/electric/replication/changes.ex#L1)

This module contains structs that are intermediate representation of Postgres and Satellite transactions.

Some of the core assumptions in this module:
- We require PK always to be present for all tables
- For now PK modification is not supported
- PG replication protocol is expected to always send the *whole* row
when dealing with UPDATE changes, and optionally old row if REPLICA
identity is set to FULL.

# `change`

```elixir
@type change() :: data_change() | Electric.Replication.Changes.TruncatedRelation.t()
```

# `data_change`

```elixir
@type data_change() ::
  Electric.Replication.Changes.NewRecord.t()
  | Electric.Replication.Changes.UpdatedRecord.t()
  | Electric.Replication.Changes.DeletedRecord.t()
```

# `db_identifier`

```elixir
@type db_identifier() :: String.t()
```

# `pk`

```elixir
@type pk() :: [String.t(), ...]
```

# `record`

```elixir
@type record() :: %{
  required(column_name :: db_identifier()) =&gt; column_data :: binary()
}
```

# `relation_id`

```elixir
@type relation_id() :: non_neg_integer()
```

# `relation_name`

```elixir
@type relation_name() :: {schema :: db_identifier(), table :: db_identifier()}
```

# `tag`

```elixir
@type tag() :: String.t()
```

Tag has the form of `origin@timestamp`, where origin is a unique source id
(UUID for Satellite clients) and timestamp is millisecond-precision UTC unix timestamp

# `xid`

```elixir
@type xid() :: Electric.Postgres.Xid.anyxid()
```

# `build_key`

Build a unique key for a given record based on its relation and PK.

Uses the `/` symbol as a PK separator, so any `/`s in the PK will
be escaped to avoid collisions.

## Examples

Build key respects PK column order:

    iex> build_key({"hello", "world"}, %{"c" => "d", "a" => "b"}, ["a", "c"])
    ~S|"hello"."world"/"b"/"d"|

    iex> build_key({"hello", "world"}, %{"a" => "b", "c" => "d"}, ["c", "a"])
    ~S|"hello"."world"/"d"/"b"|

Build key has `/` symbol in the PK escaped by repetition:

    iex> build_key({"hello", "world"}, %{"a" => "test/test", "c" => "test"}, ["a", "c"])
    ~S|"hello"."world"/"test//test"/"test"|

    iex> build_key({"hello", "world"}, %{"a" => "test", "c" => "test/test"}, ["a", "c"])
    ~S|"hello"."world"/"test"/"test//test"|

If a table has no PK, all columns are used, sorted by the column name:

    iex> build_key({"hello", "world"}, %{"c" => "d", "a" => "b"}, [])
    ~S|"hello"."world"/"b"/"d"|

    iex> build_key({"hello", "world"}, %{"a" => "1", "b" => nil, "c" => "2"}, [])
    ~S|"hello"."world"/"1"/_/"2"|

All pk sections are wrapped in quotes to allow for empty strings without generating a `//` pair.

    iex> build_key({"hello", "world"}, %{"a" => "1", "b" => "", "c" => "2"}, [])
    ~S|"hello"."world"/"1"/""/"2"|

Dots and slashes in relation names are escaped by repetition:

    iex> build_key({"a\".\"b", "c"}, %{"a" => ""}, [])
    ~S|"a".."b"."c"/""|

    iex> build_key({"a", "b\".\"c"}, %{"a" => ""}, [])
    ~S|"a"."b".."c"/""|

    iex> build_key({"a", "b"}, %{"a" => "", "b" => ""}, [])
    ~S|"a"."b"/""/""|

    iex> build_key({"a", "b\"/\""}, %{"a" => ""}, [])
    ~S|"a"."b"//""/""|

# `convert_update`

Convert an UpdatedRecord into the corresponding NewRecord or DeletedRecord
based on the provided `to` option.

## Examples

    iex> convert_update(%UpdatedRecord{record: %{id: 1}}, to: :new_record)
    %NewRecord{record: %{id: 1}}

    iex> convert_update(%UpdatedRecord{record: %{id: 2}, old_record: %{id: 1}}, to: :deleted_record)
    %DeletedRecord{old_record: %{id: 1}}

    iex> convert_update(%UpdatedRecord{record: %{id: 1}}, to: :updated_record)
    %UpdatedRecord{record: %{id: 1}}

# `fill_key`

# `filter_columns`

```elixir
@spec filter_columns(change(), [String.t()]) :: change()
```

Filter the columns of a change to include only those provided in `columns_to_keep`.

## Examples

    iex> filter_columns(%NewRecord{record: %{"a" => "b", "c" => "d"}}, ["a"])
    %NewRecord{record: %{"a" => "b"}}

    iex> filter_columns(UpdatedRecord.new(
    ...>  record: %{"a" => "b", "c" => "d"},
    ...>  old_record: %{"a" => "d", "c" => "f"}
    ...>  ), ["a"])
    UpdatedRecord.new(record: %{"a" => "b"}, old_record: %{"a" => "d"})

    iex> filter_columns(%DeletedRecord{old_record: %{"a" => "b", "c" => "d"}}, ["c"])
    %DeletedRecord{old_record: %{"c" => "d"}}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
