Currently, when row is inserted, we don't reparse its backrefs at once.
Instead, we add it to the 'rows_to_reparse' list - these backreference
links will be processed only after all updates are complete in current IDL run.

Therefore, deleting records at the same run they were inserted may cause 
problems,
there are 3 options for significant cases
1. update1:
          table A - insert
          table B refA- insert
   update2:
          table A - delete
          table B - delete

Table B would have been deleted without errors, since A already existed in 
memory
at the time of B's row creation and parsing.

2. update1:
          table B refA- insert
          table A - insert
   update2:
          table B - delete
          table A - delete

In this case B would have been deleted without any problems because B was added
to rows_to_reparse and was reparsed before deletion, thanks to commit [0]

There was an overlooked scenario where the table processing order could shift
during updates, for example:
update1:
                 table B refA- insert
                 table A - insert
update2:
                 table A - delete
                 table B - delete
In this case deleting B would trigger B.ref_a=[]

IDL process tables in the order they appear in the JSON object, i.e., in the 
order
they appear in the hash table. This means that different table processing 
orders can
occur in updates when resizing the table's hash. Currently, this corresponds to
inserting 4 → 8 → 16 elements.

In [1], a solution was proposed to not perform all deletions at once,
but to submit them for further processing after all inserts backers reparsed,
and then perform the deletions (without changing the logic of untracked 
deletions).

To reproduce this situation, test was added that inserts more than 8 tables
in the first update, and deletes only necessary rows in the second update.

[0] 
https://github.com/openvswitch/ovs/commit/02f31a1262fccab21d6ef2726a8ac877a07a7e06
[1] https://mail.openvswitch.org/pipermail/ovs-dev/2023-July/406152.html

Fixes: 02f31a1262fc ("ovsdb-idl: Preserve references for rows deleted in same 
IDL run as their insertion.")

Signed-off-by: Alexandra Rukomoinikova <[email protected]>
---
 lib/ovsdb-idl-provider.h |   1 +
 lib/ovsdb-idl.c          |  60 ++++++++++----
 tests/idltest.ovsschema  |  26 +++++++
 tests/ovsdb-idl.at       | 164 ++++++++++++++++++++++++++++-----------
 tests/test-ovsdb.c       |  51 ++++++++++++
 tests/test-ovsdb.py      |   2 +
 6 files changed, 246 insertions(+), 58 deletions(-)

diff --git a/lib/ovsdb-idl-provider.h b/lib/ovsdb-idl-provider.h
index 6cf32fb50..fcb8725ae 100644
--- a/lib/ovsdb-idl-provider.h
+++ b/lib/ovsdb-idl-provider.h
@@ -76,6 +76,7 @@ struct ovsdb_idl_row {
     struct ovsdb_datum *old_datum; /* Committed data (null if orphaned). */
     bool persist_uuid;          /* Persist 'uuid' during insert txn if set. */
     bool parsed; /* Whether the row is parsed. */
+    struct ovs_list pending_deletion_node;
     struct ovs_list reparse_node; /* Rows that needs to be re-parsed due to
                                    * insertion of a referenced row. */
 
diff --git a/lib/ovsdb-idl.c b/lib/ovsdb-idl.c
index d8094458d..c00245f92 100644
--- a/lib/ovsdb-idl.c
+++ b/lib/ovsdb-idl.c
@@ -93,6 +93,9 @@ struct ovsdb_idl {
     struct ovsdb_idl_txn *txn;
     struct hmap outstanding_txns;
     bool verify_write_only;
+    struct ovs_list pending_deletions_rows; /* Stores rows deleted in the
+                                             * current run until all inserts
+                                             * and updates will be parsed. */
     struct ovs_list deleted_untracked_rows; /* Stores rows deleted in the
                                              * current run, that are not yet
                                              * added to the track_list. */
@@ -152,7 +155,8 @@ static bool ovsdb_idl_modify_row(struct ovsdb_idl_row *,
                                  const struct shash *values, bool xor);
 static void ovsdb_idl_parse_update(struct ovsdb_idl *,
                                    const struct ovsdb_cs_update_event *);
-static void ovsdb_idl_reparse_deleted(struct ovsdb_idl *);
+static void ovsdb_idl_process_pending_deletions(struct ovsdb_idl *db);
+static void ovsdb_idl_reparse_untracked_deletions(struct ovsdb_idl *);
 static void ovsdb_idl_reparse_refs_to_inserted(struct ovsdb_idl *);
 
 static void ovsdb_idl_txn_process_reply(struct ovsdb_idl *,
@@ -266,6 +270,8 @@ ovsdb_idl_create_unconnected(const struct ovsdb_idl_class 
*class,
         .txn = NULL,
         .outstanding_txns = HMAP_INITIALIZER(&idl->outstanding_txns),
         .verify_write_only = false,
+        .pending_deletions_rows
+            = OVS_LIST_INITIALIZER(&idl->pending_deletions_rows),
         .deleted_untracked_rows
             = OVS_LIST_INITIALIZER(&idl->deleted_untracked_rows),
         .rows_to_reparse
@@ -399,10 +405,14 @@ ovsdb_idl_set_leader_only(struct ovsdb_idl *idl, bool 
leader_only)
 static void
 ovsdb_idl_clear(struct ovsdb_idl *db)
 {
-    /* Process deleted rows, removing them from the 'deleted_untracked_rows'
-     * list and reparsing their backrefs.
+    /* Process rows that have been marked for deletion. */
+    ovsdb_idl_process_pending_deletions(db);
+
+    /* Process deleted rows that are not yet added to the track_list,
+     * removing them from the 'deleted_untracked_rows' list and reparsing
+     * their backrefs.
      */
-    ovsdb_idl_reparse_deleted(db);
+    ovsdb_idl_reparse_untracked_deletions(db);
 
     /* Process backrefs of inserted rows, removing them from the
      * 'rows_to_reparse' list.
@@ -447,6 +457,7 @@ ovsdb_idl_clear(struct ovsdb_idl *db)
 
     /* Free rows deleted from tables with change tracking enabled. */
     ovsdb_idl_track_clear__(db, true);
+    ovs_assert(ovs_list_is_empty(&db->pending_deletions_rows));
     ovs_assert(ovs_list_is_empty(&db->deleted_untracked_rows));
     ovs_assert(ovs_list_is_empty(&db->rows_to_reparse));
     db->change_seqno++;
@@ -493,7 +504,8 @@ ovsdb_idl_run(struct ovsdb_idl *idl)
         ovsdb_cs_event_destroy(event);
     }
     ovsdb_idl_reparse_refs_to_inserted(idl);
-    ovsdb_idl_reparse_deleted(idl);
+    ovsdb_idl_process_pending_deletions(idl);
+    ovsdb_idl_reparse_untracked_deletions(idl);
     ovsdb_idl_row_destroy_postprocess(idl);
 }
 
@@ -1588,14 +1600,33 @@ ovsdb_idl_parse_update(struct ovsdb_idl *idl,
     }
 }
 
-/* Reparses references to rows that have been deleted in the current IDL run.
+/* Processes all rows that have been deleted in the current IDL run.
+ *
+ * This function must be called after all iserts and updates from the current
+ * IDL run have been parsed i.e., after all calls to ovsdb_idl_parse_update().
+ */
+static void
+ovsdb_idl_process_pending_deletions(struct ovsdb_idl *db)
+{
+    struct ovsdb_idl_row *row;
+
+    LIST_FOR_EACH_POP (row, pending_deletion_node,
+                       &db->pending_deletions_rows) {
+        ovs_list_init(&row->pending_deletion_node);
+
+        ovsdb_idl_delete_row(row);
+    }
+}
+
+/* Reparses references to rows that have been deleted in the current IDL run
+ * and that are not yet added to the track_list.
  *
  * To ensure that reference sources that are deleted are not reparsed,
- * this function must be called after all updates have been processed in
- * the current IDL run, i.e., after all calls to ovsdb_idl_parse_update().
+ * this function must be called after all deletions have been processed
+ * i.e., after all calls to ovsdb_idl_process_pending_deletions().
  */
 static void
-ovsdb_idl_reparse_deleted(struct ovsdb_idl *db)
+ovsdb_idl_reparse_untracked_deletions(struct ovsdb_idl *db)
 {
     struct ovsdb_idl_row *row;
 
@@ -1674,7 +1705,11 @@ ovsdb_idl_process_update(struct ovsdb_idl_table *table,
     case OVSDB_CS_ROW_DELETE:
         if (row && !ovsdb_idl_row_is_orphan(row)) {
             /* XXX perhaps we should check the 'old' values? */
-            ovsdb_idl_delete_row(row);
+            /* Defer deletion processing until all updates of the
+             * current IDL run.
+             */
+            ovs_list_push_back(&row->table->idl->pending_deletions_rows,
+                               &row->pending_deletion_node);
         } else {
             VLOG_ERR_RL(&semantic_rl, "cannot delete missing row "UUID_FMT" "
                         "from table %s",
@@ -2359,6 +2394,7 @@ ovsdb_idl_row_create__(const struct ovsdb_idl_table_class 
*class)
     class->row_init(row);
     ovs_list_init(&row->src_arcs);
     ovs_list_init(&row->dst_arcs);
+    ovs_list_init(&row->pending_deletion_node);
     ovs_list_init(&row->reparse_node);
     hmap_node_nullify(&row->txn_node);
     ovs_list_init(&row->track_node);
@@ -2489,10 +2525,6 @@ ovsdb_idl_insert_row(struct ovsdb_idl_row *row, const 
struct shash *data)
 static void
 ovsdb_idl_delete_row(struct ovsdb_idl_row *row)
 {
-    /* If row has to be reparsed, reparse it before it's deleted. */
-    if (!ovs_list_is_empty(&row->reparse_node)) {
-        ovsdb_idl_row_parse(row);
-    }
     ovsdb_idl_remove_from_indexes(row);
     ovsdb_idl_row_clear_arcs(row, true);
     ovsdb_idl_row_destroy(row);
diff --git a/tests/idltest.ovsschema b/tests/idltest.ovsschema
index 65eadb961..4263076a1 100644
--- a/tests/idltest.ovsschema
+++ b/tests/idltest.ovsschema
@@ -33,6 +33,15 @@
             },
             "min": 0
           }
+        },
+        "l3": {
+          "type": {
+            "key": {
+              "type": "uuid",
+              "refTable": "link3"
+            },
+            "min": 0
+          }
         }
       },
       "isRoot" : true
@@ -54,6 +63,23 @@
       },
       "isRoot" : true
     },
+    "link3": {
+      "columns": {
+        "i": {
+          "type": "integer"
+        },
+        "l1": {
+          "type": {
+            "key": {
+              "type": "uuid",
+              "refTable": "link1"
+            },
+            "min": 0
+          }
+        }
+      },
+      "isRoot" : true
+    },
     "indexed": {
       "columns": {
         "i": {
diff --git a/tests/ovsdb-idl.at b/tests/ovsdb-idl.at
index 728d761d4..1a9075f17 100644
--- a/tests/ovsdb-idl.at
+++ b/tests/ovsdb-idl.at
@@ -172,7 +172,7 @@ m4_define([OVSDB_CHECK_IDL_REGISTER_COLUMNS_PY],
      [simple3:name,uset,uref],
      [simple4:name],
      [simple6:name,weak_ref],
-     [link1:i,k,ka,l2],
+     [link1:i,k,ka,l2,l3],
      [link2:i,l1],
      [indexed:i],
      [singleton:name]))
@@ -755,11 +755,11 @@ OVSDB_CHECK_IDL([simple idl, conditional, multiple 
tables],
 002: simple: change conditions
 003: table simple: i=1 r=2 b=true s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] 
uuid=<1>
 004: link1: change conditions
-005: table link1: i=0 k=0 ka=[] l2= uuid=<2>
+005: table link1: i=0 k=0 ka=[] l2= l3= uuid=<2>
 005: table simple: i=1 r=2 b=true s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] 
uuid=<1>
 006: link2: change conditions
 007: {"error":null,"result":[{"uuid":["uuid","<3>"]}]}
-008: table link1: i=0 k=0 ka=[] l2= uuid=<2>
+008: table link1: i=0 k=0 ka=[] l2= l3= uuid=<2>
 008: table link2: i=3 l1= uuid=<3>
 008: table simple: i=1 r=2 b=true s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] 
uuid=<1>
 009: done
@@ -818,19 +818,19 @@ OVSDB_CHECK_IDL([self-linking idl, consistent ops],
        "row": {"k": ["uuid", "#0#"]}}]']],
   [[000: empty
 001: {"error":null,"result":[{"uuid":["uuid","<0>"]}]}
-002: table link1: i=0 k=0 ka=[] l2= uuid=<0>
+002: table link1: i=0 k=0 ka=[] l2= l3= uuid=<0>
 003: {"error":null,"result":[{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]}]}
-004: table link1: i=0 k=0 ka=[] l2= uuid=<0>
-004: table link1: i=1 k=2 ka=[] l2= uuid=<1>
-004: table link1: i=2 k=1 ka=[] l2= uuid=<2>
+004: table link1: i=0 k=0 ka=[] l2= l3= uuid=<0>
+004: table link1: i=1 k=2 ka=[] l2= l3= uuid=<1>
+004: table link1: i=2 k=1 ka=[] l2= l3= uuid=<2>
 005: {"error":null,"result":[{"count":1}]}
-006: table link1: i=0 k=0 ka=[] l2= uuid=<0>
-006: table link1: i=1 k=1 ka=[] l2= uuid=<1>
-006: table link1: i=2 k=1 ka=[] l2= uuid=<2>
+006: table link1: i=0 k=0 ka=[] l2= l3= uuid=<0>
+006: table link1: i=1 k=1 ka=[] l2= l3= uuid=<1>
+006: table link1: i=2 k=1 ka=[] l2= l3= uuid=<2>
 007: {"error":null,"result":[{"count":3}]}
-008: table link1: i=0 k=0 ka=[] l2= uuid=<0>
-008: table link1: i=1 k=0 ka=[] l2= uuid=<1>
-008: table link1: i=2 k=0 ka=[] l2= uuid=<2>
+008: table link1: i=0 k=0 ka=[] l2= l3= uuid=<0>
+008: table link1: i=1 k=0 ka=[] l2= l3= uuid=<1>
+008: table link1: i=2 k=0 ka=[] l2= l3= uuid=<2>
 009: done
 ]])
 
@@ -869,12 +869,12 @@ OVSDB_CHECK_IDL([self-linking idl, inconsistent ops],
   [[000: empty
 001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"details":"Table link1 
column k row <0> references nonexistent row <1> in table 
link1.","error":"referential integrity violation"}]}
 002: {"error":null,"result":[{"uuid":["uuid","<2>"]},{"uuid":["uuid","<3>"]}]}
-003: table link1: i=1 k=1 ka=[] l2= uuid=<2>
-003: table link1: i=2 k=1 ka=[] l2= uuid=<3>
+003: table link1: i=1 k=1 ka=[] l2= l3= uuid=<2>
+003: table link1: i=2 k=1 ka=[] l2= l3= uuid=<3>
 004: {"error":null,"result":[{"count":2},{"details":"Table link1 column k row 
<x> references nonexistent row <4> in table link1.","error":"referential 
integrity violation"}]}
 005: {"error":null,"result":[{"count":1},{"details":"cannot delete link1 row 
<2> because of 1 remaining reference(s)","error":"referential integrity 
violation"}]}
 006: {"error":null,"result":[{"count":1}]}
-007: table link1: i=1 k=1 ka=[] l2= uuid=<2>
+007: table link1: i=1 k=1 ka=[] l2= l3= uuid=<2>
 008: {"error":null,"result":[{"count":1}]}
 009: empty
 010: done
@@ -917,15 +917,15 @@ OVSDB_CHECK_IDL([self-linking idl, sets],
        "where": []}]']],
   [[000: empty
 001: 
{"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]},{"uuid":["uuid","<3>"]}]}
-002: table link1: i=0 k=0 ka=[0] l2= uuid=<0>
-002: table link1: i=1 k=0 ka=[1] l2= uuid=<1>
-002: table link1: i=2 k=0 ka=[2] l2= uuid=<2>
-002: table link1: i=3 k=0 ka=[3] l2= uuid=<3>
+002: table link1: i=0 k=0 ka=[0] l2= l3= uuid=<0>
+002: table link1: i=1 k=0 ka=[1] l2= l3= uuid=<1>
+002: table link1: i=2 k=0 ka=[2] l2= l3= uuid=<2>
+002: table link1: i=3 k=0 ka=[3] l2= l3= uuid=<3>
 003: {"error":null,"result":[{"count":4}]}
-004: table link1: i=0 k=0 ka=[0 1 2 3] l2= uuid=<0>
-004: table link1: i=1 k=0 ka=[0 1 2 3] l2= uuid=<1>
-004: table link1: i=2 k=0 ka=[0 1 2 3] l2= uuid=<2>
-004: table link1: i=3 k=0 ka=[0 1 2 3] l2= uuid=<3>
+004: table link1: i=0 k=0 ka=[0 1 2 3] l2= l3= uuid=<0>
+004: table link1: i=1 k=0 ka=[0 1 2 3] l2= l3= uuid=<1>
+004: table link1: i=2 k=0 ka=[0 1 2 3] l2= l3= uuid=<2>
+004: table link1: i=3 k=0 ka=[0 1 2 3] l2= l3= uuid=<3>
 005: {"error":null,"result":[{"count":1},{"details":"Table link1 column ka row 
<2> references nonexistent row <4> in table link1.","error":"referential 
integrity violation"}]}
 006: {"error":null,"result":[{"count":4}]}
 007: empty
@@ -945,7 +945,7 @@ OVSDB_CHECK_IDL([external-linking idl, consistent ops],
        "uuid-name": "row1"}]']],
   [[000: empty
 001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]}
-002: table link1: i=1 k=1 ka=[] l2=0 uuid=<1>
+002: table link1: i=1 k=1 ka=[] l2=0 l3= uuid=<1>
 002: table link2: i=0 l1= uuid=<0>
 003: done
 ]])
@@ -1010,8 +1010,8 @@ OVSDB_CHECK_IDL_PY([external-linking idl, insert ops],
   [['linktest']],
   [[000: empty
 001: commit, status=success
-002: table link1: i=1 k=1 ka=[1] l2= uuid=<0>
-002: table link1: i=2 k=1 ka=[1 2] l2= uuid=<1>
+002: table link1: i=1 k=1 ka=[1] l2= l3= uuid=<0>
+002: table link1: i=2 k=1 ka=[1 2] l2= l3= uuid=<1>
 003: done
 ]])
 
@@ -1020,7 +1020,7 @@ OVSDB_CHECK_IDL_PY([getattr idl, insert ops],
   [['getattrtest']],
   [[000: empty
 001: commit, status=success
-002: table link1: i=2 k=2 ka=[] l2= uuid=<0>
+002: table link1: i=2 k=2 ka=[] l2= l3= uuid=<0>
 003: done
 ]])
 
@@ -1077,19 +1077,19 @@ AT_CHECK([test-ovsdb 
'-vPATTERN:console:test-ovsdb|%c|%m' -vjsonrpc -t10 idl uni
 AT_CHECK([sort stdout | uuidfilt], [0],
     [[000: empty
 001: {"error":null,"result":[{"uuid":["uuid","<0>"]}]}
-002: table link1: i=0 k=0 ka=[] l2= uuid=<0>
+002: table link1: i=0 k=0 ka=[] l2= l3= uuid=<0>
 003: {"error":null,"result":[{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]}]}
-004: table link1: i=0 k=0 ka=[] l2= uuid=<0>
-004: table link1: i=1 k=2 ka=[] l2= uuid=<1>
-004: table link1: i=2 k=1 ka=[] l2= uuid=<2>
+004: table link1: i=0 k=0 ka=[] l2= l3= uuid=<0>
+004: table link1: i=1 k=2 ka=[] l2= l3= uuid=<1>
+004: table link1: i=2 k=1 ka=[] l2= l3= uuid=<2>
 005: {"error":null,"result":[{"count":1}]}
-006: table link1: i=0 k=0 ka=[] l2= uuid=<0>
-006: table link1: i=1 k=1 ka=[] l2= uuid=<1>
-006: table link1: i=2 k=1 ka=[] l2= uuid=<2>
+006: table link1: i=0 k=0 ka=[] l2= l3= uuid=<0>
+006: table link1: i=1 k=1 ka=[] l2= l3= uuid=<1>
+006: table link1: i=2 k=1 ka=[] l2= l3= uuid=<2>
 007: {"error":null,"result":[{"count":3}]}
-008: table link1: i=0 k=0 ka=[] l2= uuid=<0>
-008: table link1: i=1 k=0 ka=[] l2= uuid=<1>
-008: table link1: i=2 k=0 ka=[] l2= uuid=<2>
+008: table link1: i=0 k=0 ka=[] l2= l3= uuid=<0>
+008: table link1: i=1 k=0 ka=[] l2= l3= uuid=<1>
+008: table link1: i=2 k=0 ka=[] l2= l3= uuid=<2>
 009: done
 ]])
 
@@ -1097,10 +1097,12 @@ AT_CHECK([sort stdout | uuidfilt], [0],
 AT_CHECK([grep ovsdb_idl stderr | sort], [0], [dnl
 test-ovsdb|ovsdb_idl|idltest database lacks indexed table (database needs 
upgrade?)
 test-ovsdb|ovsdb_idl|idltest database lacks link2 table (database needs 
upgrade?)
+test-ovsdb|ovsdb_idl|idltest database lacks link3 table (database needs 
upgrade?)
 test-ovsdb|ovsdb_idl|idltest database lacks simple5 table (database needs 
upgrade?)
 test-ovsdb|ovsdb_idl|idltest database lacks simple6 table (database needs 
upgrade?)
 test-ovsdb|ovsdb_idl|idltest database lacks singleton table (database needs 
upgrade?)
 test-ovsdb|ovsdb_idl|link1 table in idltest database lacks l2 column (database 
needs upgrade?)
+test-ovsdb|ovsdb_idl|link1 table in idltest database lacks l3 column (database 
needs upgrade?)
 test-ovsdb|ovsdb_idl|simple7 table in idltest database lacks id column 
(database needs upgrade?)
 ])
 
@@ -1780,7 +1782,7 @@ OVSDB_CHECK_IDL_TRACK([track, simple idl, initially 
empty, strong references, in
 001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]}
 002: {"error":null,"result":[{"count":1}]}
 003: sleep
-004: table link1: inserted row: i=1 k=1 ka=[] l2= uuid=<0>
+004: table link1: inserted row: i=1 k=1 ka=[] l2= l3= uuid=<0>
 004: table link1: updated columns: i k
 005: done
 ]])
@@ -2015,7 +2017,7 @@ OVSDB_CHECK_IDL_NOTIFY([simple link idl verify notify],
 001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]}
 002: event:create, row={i=1 l2=[<1>]}, uuid=<0>, updates=None
 002: event:create, row={i=2 l1=[<0>]}, uuid=<1>, updates=None
-002: table link1: i=1 k=1 ka=[] l2=2 uuid=<0>
+002: table link1: i=1 k=1 ka=[] l2=2 l3= uuid=<0>
 002: table link2: i=2 l1=1 uuid=<1>
 003: done
 ]])
@@ -2762,14 +2764,14 @@ OVSDB_CHECK_IDL_TRACK([track, insert and delete, refs 
to link1],
 001: 
{"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]}]}
 002: {"error":null,"result":[{"count":1}]}
 003: sleep
-004: table link1: inserted row: i=1 k=1 ka=[] l2= uuid=<1>
+004: table link1: inserted row: i=1 k=1 ka=[] l2= l3= uuid=<1>
 004: table link1: updated columns: i k
 004: table link2: inserted row: i=1 l1=1 uuid=<0>
 004: table link2: inserted/deleted row: i=2 l1=1 uuid=<2>
 004: table link2: updated columns: i l1
 004: table link2: updated columns: i l1
 005: {"error":null,"result":[{"count":1}]}
-006: table link1: i=1 k=1 ka=[] l2= uuid=<1>
+006: table link1: i=1 k=1 ka=[] l2= l3= uuid=<1>
 007: done
 ]])
 OVSDB_CHECK_IDL_TRACK([track, insert and delete, refs to link2],
@@ -2807,8 +2809,8 @@ OVSDB_CHECK_IDL_TRACK([track, insert and delete, refs to 
link2],
 001: 
{"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]}]}
 002: {"error":null,"result":[{"count":1}]}
 003: sleep
-004: table link1: inserted row: i=1 k=1 ka=[] l2=1 uuid=<0>
-004: table link1: inserted/deleted row: i=2 k=1 ka=[] l2=1 uuid=<2>
+004: table link1: inserted row: i=1 k=1 ka=[] l2=1 l3= uuid=<0>
+004: table link1: inserted/deleted row: i=2 k=1 ka=[] l2=1 l3= uuid=<2>
 004: table link1: updated columns: i k l2
 004: table link1: updated columns: i k l2
 004: table link2: inserted row: i=1 l1= uuid=<1>
@@ -3057,3 +3059,77 @@ AT_CHECK([grep -qE "assertion !.*txn->assert_read_only 
failed" stderr])
 
 OVSDB_SERVER_SHUTDOWN
 AT_CLEANUP
+
+dnl This test checks the order of insert and delete operations, ensuring that
+dnl dependent rows (e.g., `link3` before `link1`) are processed correctly.
+dnl It's assumed that in the first update, link1 is processed before link3,
+dnl and in the second update, link3 is deleted before link1. We expect to see
+dnl a valid link to link3 after link1 is deleted.
+OVSDB_CHECK_IDL_TRACK([track, insert and delete: respecting dependencies],
+  [],
+  [['["idltest",
+      {"op": "insert", "table": "simple", "row": {"b": true}},
+      {"op": "insert", "table": "simple2", "row": {"name": "1"}},
+      {"op": "insert", "table": "simple3", "row": {"name": "2"}},
+      {"op": "insert", "table": "simple4", "row": {"name": "3"}},
+      {"op": "insert", "table": "simple5", "row": {"name": "4"}},
+      {"op": "insert", "table": "simple6", "row": {"name": "5"}},
+      {"op": "insert",
+       "table": "link3",
+       "uuid-name": "l3row0",
+       "row": {"i": 1}
+      },
+      {"op": "insert",
+       "table": "link2",
+       "uuid-name": "l2row0",
+       "row": {"i": 1}
+      },
+      {"op": "insert",
+       "table": "link3",
+       "uuid-name": "l3row1",
+       "row": {"i": 2}
+      },
+      {"op": "insert",
+       "table": "link1",
+       "uuid-name": "l1row0",
+       "row": {"i": 1, "k": ["named-uuid", "l1row0"], "l3": ["set", 
[["named-uuid", "l3row0"]]]}
+      }
+    ]' \
+    '+["idltest",
+      {"op": "delete",
+       "table": "link3",
+       "where": [["i", "==", 1]]},
+      {"op": "delete",
+       "table": "link1",
+       "where": [["i", "==", 1]]},
+      {"op": "delete",
+       "table": "link2",
+       "where": [["i", "==", 1]]}
+      ]' \
+    '["idltest",
+      {"op": "delete",
+       "table": "link3",
+       "where": [["i", "==", 2]]}]']],
+  [[000: empty
+001: 
{"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]},{"uuid":["uuid","<3>"]},{"uuid":["uuid","<4>"]},{"uuid":["uuid","<5>"]},{"uuid":["uuid","<6>"]},{"uuid":["uuid","<7>"]},{"uuid":["uuid","<8>"]},{"uuid":["uuid","<9>"]}]}
+002: {"error":null,"result":[{"count":1},{"count":1},{"count":1}]}
+003: table link1: inserted/deleted row: i=1 k=1 ka=[] l2= l3=1 uuid=<9>
+003: table link1: updated columns: i k l3
+003: table link2: inserted/deleted row: i=1 l1= uuid=<7>
+003: table link2: updated columns: i
+003: table link3: inserted row: i=2 l1= uuid=<8>
+003: table link3: inserted/deleted row: i=1 l1= uuid=<6>
+003: table link3: updated columns: i
+003: table link3: updated columns: i
+003: table simple3: inserted row: name=2 uset=[] uref=[] uuid=<2>
+003: table simple3: updated columns: name
+003: table simple6: inserted row: name=5 weak_ref=[] uuid=<5>
+003: table simple6: updated columns: name
+003: table simple: inserted row: i=0 r=0 b=true s= u=<10> ia=[] ra=[] ba=[] 
sa=[] ua=[] uuid=<0>
+003: table simple: updated columns: b
+004: {"error":null,"result":[{"count":1}]}
+005: table simple3: name=2 uset=[] uref=[] uuid=<2>
+005: table simple6: name=5 weak_ref=[] uuid=<5>
+005: table simple: i=0 r=0 b=true s= u=<10> ia=[] ra=[] ba=[] sa=[] ua=[] 
uuid=<0>
+006: done
+]])
diff --git a/tests/test-ovsdb.c b/tests/test-ovsdb.c
index 95290ae78..ac8c68616 100644
--- a/tests/test-ovsdb.c
+++ b/tests/test-ovsdb.c
@@ -2046,6 +2046,23 @@ print_idl_row_updated_link2(const struct idltest_link2 
*l2, int step)
     }
 }
 
+static void
+print_idl_row_updated_link3(const struct idltest_link3 *l3, int step)
+{
+    struct ds updates = DS_EMPTY_INITIALIZER;
+    for (size_t i = 0; i < IDLTEST_LINK3_N_COLUMNS; i++) {
+        if (idltest_link3_is_updated(l3, i)) {
+            ds_put_format(&updates, " %s", idltest_link3_columns[i].name);
+        }
+    }
+    if (updates.length) {
+        print_and_log("%03d: table %s: updated columns:%s",
+                      step, l3->header_.table->class_->name,
+                      ds_cstr(&updates));
+        ds_destroy(&updates);
+    }
+}
+
 static void
 print_idl_row_updated_indexed(const struct idltest_indexed *ind, int step)
 {
@@ -2188,6 +2205,11 @@ print_idl_row_link1(const struct idltest_link1 *l1, int 
step, bool terse)
         ds_put_format(&msg, "%"PRId64, l1->l2->i);
     }
 
+    ds_put_cstr(&msg, " l3=");
+    if (l1->l3) {
+        ds_put_format(&msg, "%"PRId64, l1->l3->i);
+    }
+
     char *row_msg = format_idl_row(&l1->header_, step, ds_cstr(&msg), terse);
     print_and_log("%s", row_msg);
     ds_destroy(&msg);
@@ -2213,6 +2235,23 @@ print_idl_row_link2(const struct idltest_link2 *l2, int 
step, bool terse)
     print_idl_row_updated_link2(l2, step);
 }
 
+static void
+print_idl_row_link3(const struct idltest_link3 *l3, int step, bool terse)
+{
+    struct ds msg = DS_EMPTY_INITIALIZER;
+    ds_put_format(&msg, "i=%"PRId64" l1=", l3->i);
+    if (l3->l1) {
+        ds_put_format(&msg, "%"PRId64, l3->l1->i);
+    }
+
+    char *row_msg = format_idl_row(&l3->header_, step, ds_cstr(&msg), terse);
+    print_and_log("%s", row_msg);
+    ds_destroy(&msg);
+    free(row_msg);
+
+    print_idl_row_updated_link3(l3, step);
+}
+
 static void
 print_idl_row_indexed(const struct idltest_indexed *ind, int step, bool terse)
 {
@@ -2315,6 +2354,7 @@ print_idl(struct ovsdb_idl *idl, int step, bool terse)
     const struct idltest_simple *s;
     const struct idltest_link1 *l1;
     const struct idltest_link2 *l2;
+    const struct idltest_link3 *l3;
     const struct idltest_singleton *sng;
     int n = 0;
 
@@ -2330,6 +2370,10 @@ print_idl(struct ovsdb_idl *idl, int step, bool terse)
         print_idl_row_link2(l2, step, terse);
         n++;
     }
+    IDLTEST_LINK3_FOR_EACH (l3, idl) {
+        print_idl_row_link3(l3, step, terse);
+        n++;
+    }
     IDLTEST_SIMPLE3_FOR_EACH (s3, idl) {
         print_idl_row_simple3(s3, step, terse);
         n++;
@@ -2365,6 +2409,7 @@ print_idl_track(struct ovsdb_idl *idl, int step, bool 
terse)
     const struct idltest_simple *s;
     const struct idltest_link1 *l1;
     const struct idltest_link2 *l2;
+    const struct idltest_link3 *l3;
     int n = 0;
 
     IDLTEST_SIMPLE_FOR_EACH_TRACKED (s, idl) {
@@ -2379,6 +2424,10 @@ print_idl_track(struct ovsdb_idl *idl, int step, bool 
terse)
         print_idl_row_link2(l2, step, terse);
         n++;
     }
+    IDLTEST_LINK3_FOR_EACH_TRACKED (l3, idl) {
+        print_idl_row_link3(l3, step, terse);
+        n++;
+    }
     IDLTEST_SIMPLE3_FOR_EACH_TRACKED (s3, idl) {
         print_idl_row_simple3(s3, step, terse);
         n++;
@@ -2670,6 +2719,8 @@ find_table_class(const char *name)
         return &idltest_table_link1;
     } else if (!strcmp(name, "link2")) {
         return &idltest_table_link2;
+    } else if (!strcmp(name, "link3")) {
+        return &idltest_table_link3;
     } else if (!strcmp(name, "simple3")) {
         return &idltest_table_simple3;
     } else if (!strcmp(name, "simple4")) {
diff --git a/tests/test-ovsdb.py b/tests/test-ovsdb.py
index 394897648..1b4087c16 100644
--- a/tests/test-ovsdb.py
+++ b/tests/test-ovsdb.py
@@ -281,6 +281,8 @@ def get_link1_table_printable_row(row):
         s.append("] l2=")
     if hasattr(row, "l2") and row.l2:
         s.append(str(row.l2[0].i))
+    if hasattr(row, "l3"):
+        s.append(f" l3={row.l3[0].i if row.l3 else ''}")
     return ''.join(s)
 
 
-- 
2.48.1

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to