+
+void
+ovsdb_txn_add_comment(struct ovsdb_txn *txn, const char *s)
+{
+ if (txn->comment.length) {
+ ds_put_char(&txn->comment, '\n');
+ }
+ ds_put_cstr(&txn->comment, s);
+}
+
+const char *
+ovsdb_txn_get_comment(const struct ovsdb_txn *txn)
+{
+ return txn->comment.length ? ds_cstr_ro(&txn->comment) : NULL;
+}
+\f
+static void
+ovsdb_txn_row_prefree(struct ovsdb_txn_row *txn_row)
+{
+ struct ovsdb_row *row = txn_row->old ? txn_row->old : txn_row->new;
+ struct ovsdb_txn_table *txn_table = row->table->txn_table;
+
+ txn_table->n_processed--;
+ hmap_remove(&txn_table->txn_rows, &txn_row->hmap_node);
+
+ if (txn_row->old) {
+ txn_row->old->txn_row = NULL;
+ }
+ if (txn_row->new) {
+ txn_row->new->txn_row = NULL;
+ }
+}
+
+static void
+ovsdb_txn_table_destroy(struct ovsdb_txn_table *txn_table)
+{
+ assert(hmap_is_empty(&txn_table->txn_rows));
+ txn_table->table->txn_table = NULL;
+ hmap_destroy(&txn_table->txn_rows);
+ list_remove(&txn_table->node);
+ free(txn_table);
+}
+
+/* Calls 'cb' for every txn_row within 'txn'. If 'cb' returns nonnull, this
+ * aborts the iteration and for_each_txn_row() passes the error up. Otherwise,
+ * returns a null pointer after iteration is complete.
+ *
+ * 'cb' may insert new txn_rows and new txn_tables into 'txn'. It may delete
+ * the txn_row that it is passed in, or txn_rows in txn_tables other than the
+ * one passed to 'cb'. It may *not* delete txn_rows other than the one passed
+ * in within the same txn_table. It may *not* delete any txn_tables. As long
+ * as these rules are followed, 'cb' will be called exactly once for each
+ * txn_row in 'txn', even those added by 'cb'.
+ */
+static struct ovsdb_error * WARN_UNUSED_RESULT
+for_each_txn_row(struct ovsdb_txn *txn,
+ struct ovsdb_error *(*cb)(struct ovsdb_txn *,
+ struct ovsdb_txn_row *))
+{
+ bool any_work;
+
+ serial++;
+
+ do {
+ struct ovsdb_txn_table *t, *next_txn_table;
+
+ any_work = false;
+ LIST_FOR_EACH_SAFE (t, next_txn_table, node, &txn->txn_tables) {
+ if (t->serial != serial) {
+ t->serial = serial;
+ t->n_processed = 0;
+ }
+
+ while (t->n_processed < hmap_count(&t->txn_rows)) {
+ struct ovsdb_txn_row *r, *next_txn_row;
+
+ HMAP_FOR_EACH_SAFE (r, next_txn_row, hmap_node, &t->txn_rows) {
+ if (r->serial != serial) {
+ struct ovsdb_error *error;
+
+ r->serial = serial;
+ t->n_processed++;
+ any_work = true;
+
+ error = cb(txn, r);
+ if (error) {
+ return error;
+ }
+ }
+ }
+ }
+ if (hmap_is_empty(&t->txn_rows)) {
+ /* Table is empty. Drop it. */
+ ovsdb_txn_table_destroy(t);
+ }
+ }
+ } while (any_work);
+
+ return NULL;
+}