@@ -90,6 +90,124 @@ func TestEmptyTypeSchema(t *testing.T) {
9090 x .ParseNamespaceAttr (types [0 ].TypeName )
9191}
9292
93+ func TestDeleteSetWithVarEdgeCorruptsData (t * testing.T ) {
94+ // Setup temporary directory for Badger DB
95+ dir , err := os .MkdirTemp ("" , "storetest_" )
96+ require .NoError (t , err )
97+ defer os .RemoveAll (dir )
98+
99+ opt := badger .DefaultOptions (dir )
100+ ps , err := badger .OpenManaged (opt )
101+ require .NoError (t , err )
102+ posting .Init (ps , 0 , false )
103+ Init (ps )
104+
105+ // Set schema
106+ schemaTxt := `
107+ room: string @index(hash) @upsert .
108+ person: string @index(hash) @upsert .
109+ office: uid @reverse @count .
110+ `
111+ err = schema .ParseBytes ([]byte (schemaTxt ), 1 )
112+ require .NoError (t , err )
113+
114+ ctx := context .Background ()
115+ attrRoom := x .AttrInRootNamespace ("room" )
116+ attrPerson := x .AttrInRootNamespace ("person" )
117+ attrOffice := x .AttrInRootNamespace ("office" )
118+
119+ uidRoom := uint64 (1 )
120+ uidJohn := uint64 (2 )
121+
122+ runMutation := func (startTs , commitTs uint64 , edges []* pb.DirectedEdge ) {
123+ txn := posting .Oracle ().RegisterStartTs (startTs )
124+ for _ , edge := range edges {
125+ require .NoError (t , runMutation (ctx , edge , txn ))
126+ }
127+ txn .Update ()
128+ writer := posting .NewTxnWriter (ps )
129+ require .NoError (t , txn .CommitToDisk (writer , commitTs ))
130+ require .NoError (t , writer .Flush ())
131+ txn .UpdateCachedKeys (commitTs )
132+ }
133+
134+ // Initial mutation: Set John → Leopard
135+ runMutation (1 , 3 , []* pb.DirectedEdge {
136+ {
137+ Entity : uidJohn ,
138+ Attr : attrPerson ,
139+ Value : []byte ("John Smith" ),
140+ ValueType : pb .Posting_STRING ,
141+ Op : pb .DirectedEdge_SET ,
142+ },
143+ {
144+ Entity : uidRoom ,
145+ Attr : attrRoom ,
146+ Value : []byte ("Leopard" ),
147+ ValueType : pb .Posting_STRING ,
148+ Op : pb .DirectedEdge_SET ,
149+ },
150+ {
151+ Entity : uidJohn ,
152+ Attr : attrOffice ,
153+ ValueId : uidRoom ,
154+ ValueType : pb .Posting_UID ,
155+ Op : pb .DirectedEdge_SET ,
156+ },
157+ })
158+
159+ key := x .DataKey (attrOffice , uidJohn )
160+ rollup (t , key , ps , 4 )
161+
162+ // Second mutation: Remove John from Leopard, assign Amanda
163+ uidAmanda := uint64 (3 )
164+
165+ runMutation (6 , 8 , []* pb.DirectedEdge {
166+ {
167+ Entity : uidJohn ,
168+ Attr : attrOffice ,
169+ ValueId : uidRoom ,
170+ ValueType : pb .Posting_UID ,
171+ Op : pb .DirectedEdge_DEL ,
172+ },
173+ {
174+ Entity : uidAmanda ,
175+ Attr : attrPerson ,
176+ Value : []byte ("Amanda Anderson" ),
177+ ValueType : pb .Posting_STRING ,
178+ Op : pb .DirectedEdge_SET ,
179+ },
180+ {
181+ Entity : uidAmanda ,
182+ Attr : attrOffice ,
183+ ValueId : uidRoom ,
184+ ValueType : pb .Posting_UID ,
185+ Op : pb .DirectedEdge_SET ,
186+ },
187+ })
188+
189+ // Read and validate: Amanda assigned, John unassigned
190+ txnRead := posting .Oracle ().RegisterStartTs (10 )
191+
192+ list , err := txnRead .Get (key )
193+ require .NoError (t , err )
194+
195+ uids , err := list .Uids (posting.ListOptions {ReadTs : 10 })
196+ require .NoError (t , err )
197+
198+ // This assertion FAILS in the broken case where both Amanda and John are assigned
199+ require .Equal (t , 0 , len (uids .Uids ), "John should no longer have an office assigned" )
200+
201+ keyRev := x .ReverseKey (attrOffice , uidRoom )
202+ listRev , err := txnRead .Get (keyRev )
203+ require .NoError (t , err )
204+
205+ reverseUids , err := listRev .Uids (posting.ListOptions {ReadTs : 10 })
206+ require .NoError (t , err )
207+
208+ require .Equal (t , []uint64 {uidAmanda }, reverseUids .Uids , "Only Amanda should be assigned on reverse edge" )
209+ }
210+
93211func TestGetScalarList (t * testing.T ) {
94212 dir , err := os .MkdirTemp ("" , "storetest_" )
95213 x .Check (err )
0 commit comments