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