Skip to content

Commit fb85aac

Browse files
committed
Fix index changes
1 parent cefdb1d commit fb85aac

2 files changed

Lines changed: 25 additions & 15 deletions

File tree

packages/sync-service/lib/electric/shapes/consumer/event_handler/subqueries/index_changes.ex

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ defmodule Electric.Shapes.Consumer.EventHandler.Subqueries.IndexChanges do
2020
2121
For a negated (`NOT IN`) subquery:
2222
- Adding values to the index *narrows* the filter (fewer rows match).
23-
- So a move-in does **not** update the index at enqueue time (keeping the
24-
filter broad); the add is deferred until **complete**.
23+
- So a dependency move-in does **not** update the index at enqueue time
24+
(keeping the filter broad); the add is deferred until **complete**.
25+
- A dependency move-out broadens the filter by removing the value from the
26+
index immediately, and that removal remains correct after the splice.
2527
2628
## Effect tables
2729
@@ -31,8 +33,8 @@ defmodule Electric.Shapes.Consumer.EventHandler.Subqueries.IndexChanges do
3133
|------------|----------|---------------------------|
3234
| move_in | positive | AddToSubqueryIndex |
3335
| move_in | negated | *(none)* |
34-
| move_out | positive | RemoveFromSubqueryIndex |
35-
| move_out | negated | *(none)* |
36+
| move_out | positive | *(none)* |
37+
| move_out | negated | RemoveFromSubqueryIndex |
3638
3739
### When complete (splice finished, or immediate for non-buffering cases)
3840
@@ -58,9 +60,11 @@ defmodule Electric.Shapes.Consumer.EventHandler.Subqueries.IndexChanges do
5860
@type move :: {:move_in | :move_out, non_neg_integer(), list()}
5961

6062
@doc """
61-
Returns index effects to apply when a dependency move event is first enqueued.
63+
Returns index effects to apply when a dependency move event starts buffering.
6264
63-
Used by buffering cases to broaden the filter before the move-in query runs.
65+
Used only by buffering cases to broaden the filter before the move-in query
66+
runs. Calling this for an immediate (non-buffering) move is a bug in the
67+
caller.
6468
"""
6569
@spec effects_for_enqueue(DnfPlan.t(), move(), [String.t()]) ::
6670
[Effect.AddToSubqueryIndex.t() | Effect.RemoveFromSubqueryIndex.t()]
@@ -77,10 +81,7 @@ defmodule Electric.Shapes.Consumer.EventHandler.Subqueries.IndexChanges do
7781
}
7882
]
7983

80-
{:negated, :move_in} ->
81-
[]
82-
83-
{:positive, :move_out} ->
84+
{:negated, :move_out} ->
8485
[
8586
%Effect.RemoveFromSubqueryIndex{
8687
dep_index: dep_index,
@@ -89,8 +90,9 @@ defmodule Electric.Shapes.Consumer.EventHandler.Subqueries.IndexChanges do
8990
}
9091
]
9192

92-
{:negated, :move_out} ->
93-
[]
93+
other ->
94+
raise ArgumentError,
95+
"effects_for_enqueue/3 only supports buffering cases, got #{inspect(other)}"
9496
end
9597
end
9698

packages/sync-service/test/electric/shapes/consumer/event_handler_test.exs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,21 @@ defmodule Electric.Shapes.Consumer.EventHandlerTest do
7272
handler = new_handler(shape: negated_shape(), subquery_view: MapSet.new([1]))
7373
dep_handle = dep_handle(handler)
7474

75-
# Case B: negated move-out → no index effect at enqueue (filter stays broad)
75+
# Case B: negated move-out → remove the value at enqueue time so the
76+
# negated index reflects the post-move exclusion set while buffering.
7677
assert {:ok, %Buffering{} = handler, plan} =
7778
EventHandler.handle_event(
7879
handler,
7980
{:materializer_changes, dep_handle, %{move_in: [], move_out: [{1, "1"}]}}
8081
)
8182

82-
assert %Plan{effects: [%Effect.SubscribeGlobalLsn{}, %Effect.StartMoveInQuery{}]} = plan
83+
assert %Plan{
84+
effects: [
85+
%Effect.RemoveFromSubqueryIndex{dep_index: 0, values: [{1, "1"}]},
86+
%Effect.SubscribeGlobalLsn{},
87+
%Effect.StartMoveInQuery{}
88+
]
89+
} = plan
8390

8491
assert %Buffering{
8592
views_before_move: %{["$sublink", "0"] => before_view},
@@ -98,7 +105,8 @@ defmodule Electric.Shapes.Consumer.EventHandlerTest do
98105
{:query_move_in_complete, [child_insert("99", "1")], lsn(10)}
99106
)
100107

101-
# Case B: negated move-out → no index effect at complete either
108+
# Case B: negated move-out → no further index effect at complete because
109+
# the enqueue-time removal already matches the post-splice dependency view.
102110
assert {:ok, %Steady{views: %{["$sublink", "0"] => view}}, plan} =
103111
EventHandler.handle_event(handler, global_last_seen_lsn(10))
104112

0 commit comments

Comments
 (0)