+static struct ovsdb_row *
+ovsdb_index_search(struct hmap *index, struct ovsdb_row *row, size_t i,
+ uint32_t hash)
+{
+ const struct ovsdb_table *table = row->table;
+ const struct ovsdb_column_set *columns = &table->schema->indexes[i];
+ struct hmap_node *node;
+
+ for (node = hmap_first_with_hash(index, hash); node;
+ node = hmap_next_with_hash(node)) {
+ struct ovsdb_row *irow = ovsdb_row_from_index_node(node, table, i);
+ if (ovsdb_row_equal_columns(row, irow, columns)) {
+ return irow;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+duplicate_index_row__(const struct ovsdb_column_set *index,
+ const struct ovsdb_row *row,
+ const char *title,
+ struct ds *out)
+{
+ size_t n_columns = shash_count(&row->table->schema->columns);
+
+ ds_put_format(out, "%s row, with UUID "UUID_FMT", ",
+ title, UUID_ARGS(ovsdb_row_get_uuid(row)));
+ if (!row->txn_row
+ || bitmap_scan(row->txn_row->changed, 0, n_columns) == n_columns) {
+ ds_put_cstr(out, "existed in the database before this "
+ "transaction and was not modified by the transaction.");
+ } else if (!row->txn_row->old) {
+ ds_put_cstr(out, "was inserted by this transaction.");
+ } else if (ovsdb_row_equal_columns(row->txn_row->old,
+ row->txn_row->new, index)) {
+ ds_put_cstr(out, "existed in the database before this "
+ "transaction, which modified some of the row's columns "
+ "but not any columns in this index.");
+ } else {
+ ds_put_cstr(out, "had the following index values before the "
+ "transaction: ");
+ ovsdb_row_columns_to_string(row->txn_row->old, index, out);
+ ds_put_char(out, '.');
+ }
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+duplicate_index_row(const struct ovsdb_column_set *index,
+ const struct ovsdb_row *a,
+ const struct ovsdb_row *b)
+{
+ struct ovsdb_column_set all_columns;
+ struct ovsdb_error *error;
+ char *index_s;
+ struct ds s;
+
+ /* Put 'a' and 'b' in a predictable order to make error messages
+ * reproducible for testing. */
+ ovsdb_column_set_init(&all_columns);
+ ovsdb_column_set_add_all(&all_columns, a->table);
+ if (ovsdb_row_compare_columns_3way(a, b, &all_columns) < 0) {
+ const struct ovsdb_row *tmp = a;
+ a = b;
+ b = tmp;
+ }
+ ovsdb_column_set_destroy(&all_columns);
+
+ index_s = ovsdb_column_set_to_string(index);
+
+ ds_init(&s);
+ ds_put_format(&s, "Transaction causes multiple rows in \"%s\" table to "
+ "have identical values (", a->table->schema->name);
+ ovsdb_row_columns_to_string(a, index, &s);
+ ds_put_format(&s, ") for index on %s. ", index_s);
+ duplicate_index_row__(index, a, "First", &s);
+ ds_put_cstr(&s, " ");
+ duplicate_index_row__(index, b, "Second", &s);
+
+ free(index_s);
+
+ error = ovsdb_error("constraint violation", "%s", ds_cstr(&s));
+ ds_destroy(&s);
+ return error;
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+check_index_uniqueness(struct ovsdb_txn *txn OVS_UNUSED,
+ struct ovsdb_txn_row *txn_row)
+{
+ struct ovsdb_txn_table *txn_table = txn_row->table->txn_table;
+ struct ovsdb_table *table = txn_row->table;
+ struct ovsdb_row *row = txn_row->new;
+ size_t i;
+
+ if (!row) {
+ return NULL;
+ }
+
+ for (i = 0; i < table->schema->n_indexes; i++) {
+ const struct ovsdb_column_set *index = &table->schema->indexes[i];
+ struct ovsdb_row *irow;
+ uint32_t hash;
+
+ hash = ovsdb_row_hash_columns(row, index, 0);
+ irow = ovsdb_index_search(&txn_table->txn_indexes[i], row, i, hash);
+ if (irow) {
+ return duplicate_index_row(index, irow, row);
+ }
+
+ irow = ovsdb_index_search(&table->indexes[i], row, i, hash);
+ if (irow && !irow->txn_row) {
+ return duplicate_index_row(index, irow, row);
+ }
+
+ hmap_insert(&txn_table->txn_indexes[i],
+ ovsdb_row_get_index_node(row, i), hash);
+ }
+
+ return NULL;
+}
+