]>
Commit | Line | Data |
---|---|---|
1dd5b71d | 1 | /* Copyright (c) 2009, 2010, 2011 Nicira Networks |
f85f8ebb BP |
2 | * |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at: | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | #include <config.h> | |
17 | ||
18 | #include "transaction.h" | |
19 | ||
20 | #include <assert.h> | |
21 | ||
17d18afb | 22 | #include "bitmap.h" |
d171b584 | 23 | #include "dynamic-string.h" |
f85f8ebb BP |
24 | #include "hash.h" |
25 | #include "hmap.h" | |
26 | #include "json.h" | |
71c93bd4 | 27 | #include "list.h" |
f85f8ebb BP |
28 | #include "ovsdb-error.h" |
29 | #include "ovsdb.h" | |
30 | #include "row.h" | |
31 | #include "table.h" | |
32 | #include "uuid.h" | |
33 | ||
34 | struct ovsdb_txn { | |
35 | struct ovsdb *db; | |
71c93bd4 | 36 | struct list txn_tables; /* Contains "struct ovsdb_txn_table"s. */ |
d171b584 | 37 | struct ds comment; |
f85f8ebb BP |
38 | }; |
39 | ||
40 | /* A table modified by a transaction. */ | |
41 | struct ovsdb_txn_table { | |
71c93bd4 | 42 | struct list node; /* Element in ovsdb_txn's txn_tables list. */ |
f85f8ebb BP |
43 | struct ovsdb_table *table; |
44 | struct hmap txn_rows; /* Contains "struct ovsdb_txn_row"s. */ | |
3e010b7a BP |
45 | |
46 | /* Used by for_each_txn_row(). */ | |
47 | unsigned int serial; /* Serial number of in-progress iteration. */ | |
48 | unsigned int n_processed; /* Number of rows processed. */ | |
f85f8ebb BP |
49 | }; |
50 | ||
51 | /* A row modified by the transaction: | |
52 | * | |
53 | * - A row added by a transaction will have null 'old' and non-null 'new'. | |
54 | * | |
55 | * - A row deleted by a transaction will have non-null 'old' and null | |
56 | * 'new'. | |
57 | * | |
58 | * - A row modified by a transaction will have non-null 'old' and 'new'. | |
59 | * | |
60 | * - 'old' and 'new' both null is invalid. It would indicate that a row | |
61 | * was added then deleted within a single transaction, but we instead | |
62 | * handle that case by deleting the txn_row entirely. | |
63 | */ | |
64 | struct ovsdb_txn_row { | |
65 | struct hmap_node hmap_node; /* In ovsdb_txn_table's txn_rows hmap. */ | |
66 | struct ovsdb_row *old; /* The old row. */ | |
67 | struct ovsdb_row *new; /* The new row. */ | |
c7d85e0d | 68 | size_t n_refs; /* Number of remaining references. */ |
3e010b7a BP |
69 | |
70 | /* Used by for_each_txn_row(). */ | |
71 | unsigned int serial; /* Serial number of in-progress commit. */ | |
17d18afb BP |
72 | |
73 | unsigned long changed[]; /* Bits set to 1 for columns that changed. */ | |
f85f8ebb BP |
74 | }; |
75 | ||
3e010b7a BP |
76 | static void ovsdb_txn_row_prefree(struct ovsdb_txn_row *); |
77 | static struct ovsdb_error * WARN_UNUSED_RESULT | |
78 | for_each_txn_row(struct ovsdb_txn *txn, | |
79 | struct ovsdb_error *(*)(struct ovsdb_txn *, | |
80 | struct ovsdb_txn_row *)); | |
81 | ||
82 | /* Used by for_each_txn_row() to track tables and rows that have been | |
83 | * processed. */ | |
84 | static unsigned int serial; | |
85 | ||
f85f8ebb BP |
86 | struct ovsdb_txn * |
87 | ovsdb_txn_create(struct ovsdb *db) | |
88 | { | |
89 | struct ovsdb_txn *txn = xmalloc(sizeof *txn); | |
90 | txn->db = db; | |
71c93bd4 | 91 | list_init(&txn->txn_tables); |
d171b584 | 92 | ds_init(&txn->comment); |
f85f8ebb BP |
93 | return txn; |
94 | } | |
95 | ||
96 | static void | |
3e010b7a | 97 | ovsdb_txn_free(struct ovsdb_txn *txn) |
f85f8ebb | 98 | { |
3e010b7a | 99 | assert(list_is_empty(&txn->txn_tables)); |
d171b584 | 100 | ds_destroy(&txn->comment); |
f85f8ebb BP |
101 | free(txn); |
102 | } | |
103 | ||
17d18afb | 104 | static struct ovsdb_error * |
3e010b7a BP |
105 | ovsdb_txn_row_abort(struct ovsdb_txn *txn OVS_UNUSED, |
106 | struct ovsdb_txn_row *txn_row) | |
f85f8ebb BP |
107 | { |
108 | struct ovsdb_row *old = txn_row->old; | |
109 | struct ovsdb_row *new = txn_row->new; | |
110 | ||
3e010b7a | 111 | ovsdb_txn_row_prefree(txn_row); |
f85f8ebb BP |
112 | if (!old) { |
113 | hmap_remove(&new->table->rows, &new->hmap_node); | |
114 | } else if (!new) { | |
115 | hmap_insert(&old->table->rows, &old->hmap_node, ovsdb_row_hash(old)); | |
116 | } else { | |
117 | hmap_replace(&new->table->rows, &new->hmap_node, &old->hmap_node); | |
118 | } | |
119 | ovsdb_row_destroy(new); | |
3e010b7a BP |
120 | free(txn_row); |
121 | ||
122 | return NULL; | |
f85f8ebb BP |
123 | } |
124 | ||
125 | void | |
126 | ovsdb_txn_abort(struct ovsdb_txn *txn) | |
127 | { | |
3e010b7a BP |
128 | ovsdb_error_assert(for_each_txn_row(txn, ovsdb_txn_row_abort)); |
129 | ovsdb_txn_free(txn); | |
f85f8ebb BP |
130 | } |
131 | ||
0d0f05b9 BP |
132 | static struct ovsdb_txn_row * |
133 | find_txn_row(const struct ovsdb_table *table, const struct uuid *uuid) | |
134 | { | |
135 | struct ovsdb_txn_row *txn_row; | |
136 | ||
137 | if (!table->txn_table) { | |
138 | return NULL; | |
139 | } | |
140 | ||
4e8e4213 | 141 | HMAP_FOR_EACH_WITH_HASH (txn_row, hmap_node, |
0d0f05b9 BP |
142 | uuid_hash(uuid), &table->txn_table->txn_rows) { |
143 | const struct ovsdb_row *row; | |
144 | ||
145 | row = txn_row->old ? txn_row->old : txn_row->new; | |
146 | if (uuid_equals(uuid, ovsdb_row_get_uuid(row))) { | |
147 | return txn_row; | |
148 | } | |
149 | } | |
150 | ||
151 | return NULL; | |
152 | } | |
153 | ||
c7d85e0d | 154 | static struct ovsdb_error * WARN_UNUSED_RESULT |
97f7803b BP |
155 | ovsdb_txn_adjust_atom_refs(struct ovsdb_txn *txn, const struct ovsdb_row *r, |
156 | const struct ovsdb_column *c, | |
c7d85e0d BP |
157 | const struct ovsdb_base_type *base, |
158 | const union ovsdb_atom *atoms, unsigned int n, | |
159 | int delta) | |
0d0f05b9 | 160 | { |
c7d85e0d | 161 | const struct ovsdb_table *table; |
0d0f05b9 BP |
162 | unsigned int i; |
163 | ||
7360012b | 164 | if (!ovsdb_base_type_is_strong_ref(base)) { |
c7d85e0d BP |
165 | return NULL; |
166 | } | |
167 | ||
168 | table = base->u.uuid.refTable; | |
0d0f05b9 BP |
169 | for (i = 0; i < n; i++) { |
170 | const struct uuid *uuid = &atoms[i].uuid; | |
171 | struct ovsdb_txn_row *txn_row = find_txn_row(table, uuid); | |
c7d85e0d BP |
172 | if (!txn_row) { |
173 | const struct ovsdb_row *row = ovsdb_table_get_row(table, uuid); | |
174 | if (row) { | |
175 | txn_row = ovsdb_txn_row_modify(txn, row)->txn_row; | |
0d0f05b9 | 176 | } else { |
c7d85e0d | 177 | return ovsdb_error("referential integrity violation", |
97f7803b BP |
178 | "Table %s column %s row "UUID_FMT" " |
179 | "references nonexistent row "UUID_FMT" in " | |
180 | "table %s.", | |
181 | r->table->schema->name, c->name, | |
182 | UUID_ARGS(ovsdb_row_get_uuid(r)), | |
183 | UUID_ARGS(uuid), table->schema->name); | |
0d0f05b9 BP |
184 | } |
185 | } | |
c7d85e0d | 186 | txn_row->n_refs += delta; |
0d0f05b9 | 187 | } |
c7d85e0d BP |
188 | |
189 | return NULL; | |
0d0f05b9 BP |
190 | } |
191 | ||
c7d85e0d BP |
192 | static struct ovsdb_error * WARN_UNUSED_RESULT |
193 | ovsdb_txn_adjust_row_refs(struct ovsdb_txn *txn, const struct ovsdb_row *r, | |
194 | const struct ovsdb_column *column, int delta) | |
0d0f05b9 BP |
195 | { |
196 | const struct ovsdb_datum *field = &r->fields[column->index]; | |
c7d85e0d | 197 | struct ovsdb_error *error; |
0d0f05b9 | 198 | |
97f7803b | 199 | error = ovsdb_txn_adjust_atom_refs(txn, r, column, &column->type.key, |
c7d85e0d BP |
200 | field->keys, field->n, delta); |
201 | if (!error) { | |
97f7803b | 202 | error = ovsdb_txn_adjust_atom_refs(txn, r, column, &column->type.value, |
c7d85e0d | 203 | field->values, field->n, delta); |
0d0f05b9 | 204 | } |
c7d85e0d | 205 | return error; |
0d0f05b9 BP |
206 | } |
207 | ||
208 | static struct ovsdb_error * WARN_UNUSED_RESULT | |
c7d85e0d | 209 | update_row_ref_count(struct ovsdb_txn *txn, struct ovsdb_txn_row *r) |
0d0f05b9 | 210 | { |
c7d85e0d BP |
211 | struct ovsdb_table *table = r->old ? r->old->table : r->new->table; |
212 | struct shash_node *node; | |
213 | ||
214 | SHASH_FOR_EACH (node, &table->schema->columns) { | |
215 | const struct ovsdb_column *column = node->data; | |
216 | struct ovsdb_error *error; | |
217 | ||
218 | if (r->old) { | |
219 | error = ovsdb_txn_adjust_row_refs(txn, r->old, column, -1); | |
220 | if (error) { | |
1dd5b71d | 221 | return OVSDB_WRAP_BUG("error decreasing refcount", error); |
c7d85e0d BP |
222 | } |
223 | } | |
224 | if (r->new) { | |
225 | error = ovsdb_txn_adjust_row_refs(txn, r->new, column, 1); | |
226 | if (error) { | |
227 | return error; | |
0d0f05b9 BP |
228 | } |
229 | } | |
230 | } | |
0d0f05b9 | 231 | |
c7d85e0d | 232 | return NULL; |
0d0f05b9 BP |
233 | } |
234 | ||
235 | static struct ovsdb_error * WARN_UNUSED_RESULT | |
c7d85e0d | 236 | check_ref_count(struct ovsdb_txn *txn OVS_UNUSED, struct ovsdb_txn_row *r) |
0d0f05b9 | 237 | { |
c7d85e0d BP |
238 | if (r->new || !r->n_refs) { |
239 | return NULL; | |
240 | } else { | |
241 | return ovsdb_error("referential integrity violation", | |
242 | "cannot delete %s row "UUID_FMT" because " | |
243 | "of %zu remaining reference(s)", | |
244 | r->old->table->schema->name, | |
245 | UUID_ARGS(ovsdb_row_get_uuid(r->old)), | |
246 | r->n_refs); | |
0d0f05b9 | 247 | } |
0d0f05b9 BP |
248 | } |
249 | ||
250 | static struct ovsdb_error * WARN_UNUSED_RESULT | |
251 | update_ref_counts(struct ovsdb_txn *txn) | |
252 | { | |
253 | struct ovsdb_error *error; | |
0d0f05b9 | 254 | |
c7d85e0d | 255 | error = for_each_txn_row(txn, update_row_ref_count); |
0d0f05b9 BP |
256 | if (error) { |
257 | return error; | |
258 | } | |
259 | ||
c7d85e0d | 260 | return for_each_txn_row(txn, check_ref_count); |
0d0f05b9 BP |
261 | } |
262 | ||
17d18afb | 263 | static struct ovsdb_error * |
3e010b7a BP |
264 | ovsdb_txn_row_commit(struct ovsdb_txn *txn OVS_UNUSED, |
265 | struct ovsdb_txn_row *txn_row) | |
266 | { | |
267 | ovsdb_txn_row_prefree(txn_row); | |
c7d85e0d BP |
268 | if (txn_row->new) { |
269 | txn_row->new->n_refs = txn_row->n_refs; | |
270 | } | |
3e010b7a BP |
271 | ovsdb_row_destroy(txn_row->old); |
272 | free(txn_row); | |
273 | ||
274 | return NULL; | |
275 | } | |
276 | ||
7360012b BP |
277 | static void |
278 | add_weak_ref(struct ovsdb_txn *txn, | |
279 | const struct ovsdb_row *src_, const struct ovsdb_row *dst_) | |
280 | { | |
281 | struct ovsdb_row *src = (struct ovsdb_row *) src_; | |
282 | struct ovsdb_row *dst = (struct ovsdb_row *) dst_; | |
283 | struct ovsdb_weak_ref *weak; | |
284 | ||
285 | if (src == dst) { | |
286 | return; | |
287 | } | |
288 | ||
289 | dst = ovsdb_txn_row_modify(txn, dst); | |
290 | ||
291 | if (!list_is_empty(&dst->dst_refs)) { | |
292 | /* Omit duplicates. */ | |
293 | weak = CONTAINER_OF(list_back(&dst->dst_refs), | |
294 | struct ovsdb_weak_ref, dst_node); | |
295 | if (weak->src == src) { | |
296 | return; | |
297 | } | |
298 | } | |
299 | ||
300 | weak = xmalloc(sizeof *weak); | |
301 | weak->src = src; | |
302 | list_push_back(&dst->dst_refs, &weak->dst_node); | |
303 | list_push_back(&src->src_refs, &weak->src_node); | |
304 | } | |
305 | ||
306 | static struct ovsdb_error * WARN_UNUSED_RESULT | |
307 | assess_weak_refs(struct ovsdb_txn *txn, struct ovsdb_txn_row *txn_row) | |
308 | { | |
309 | struct ovsdb_table *table; | |
310 | struct shash_node *node; | |
311 | ||
312 | if (txn_row->old) { | |
313 | /* Mark rows that have weak references to 'txn_row' as modified, so | |
314 | * that their weak references will get reassessed. */ | |
315 | struct ovsdb_weak_ref *weak, *next; | |
316 | ||
4e8e4213 | 317 | LIST_FOR_EACH_SAFE (weak, next, dst_node, &txn_row->old->dst_refs) { |
7360012b BP |
318 | if (!weak->src->txn_row) { |
319 | ovsdb_txn_row_modify(txn, weak->src); | |
320 | } | |
321 | } | |
322 | } | |
323 | ||
324 | if (!txn_row->new) { | |
325 | /* We don't have to do anything about references that originate at | |
326 | * 'txn_row', because ovsdb_row_destroy() will remove those weak | |
327 | * references. */ | |
328 | return NULL; | |
329 | } | |
330 | ||
331 | table = txn_row->new->table; | |
332 | SHASH_FOR_EACH (node, &table->schema->columns) { | |
333 | const struct ovsdb_column *column = node->data; | |
334 | struct ovsdb_datum *datum = &txn_row->new->fields[column->index]; | |
335 | unsigned int orig_n, i; | |
336 | bool zero = false; | |
337 | ||
338 | orig_n = datum->n; | |
339 | ||
340 | if (ovsdb_base_type_is_weak_ref(&column->type.key)) { | |
341 | for (i = 0; i < datum->n; ) { | |
342 | const struct ovsdb_row *row; | |
343 | ||
344 | row = ovsdb_table_get_row(column->type.key.u.uuid.refTable, | |
345 | &datum->keys[i].uuid); | |
346 | if (row) { | |
347 | add_weak_ref(txn, txn_row->new, row); | |
348 | i++; | |
349 | } else { | |
350 | if (uuid_is_zero(&datum->keys[i].uuid)) { | |
351 | zero = true; | |
352 | } | |
353 | ovsdb_datum_remove_unsafe(datum, i, &column->type); | |
354 | } | |
355 | } | |
356 | } | |
357 | ||
358 | if (ovsdb_base_type_is_weak_ref(&column->type.value)) { | |
359 | for (i = 0; i < datum->n; ) { | |
360 | const struct ovsdb_row *row; | |
361 | ||
362 | row = ovsdb_table_get_row(column->type.value.u.uuid.refTable, | |
363 | &datum->values[i].uuid); | |
364 | if (row) { | |
365 | add_weak_ref(txn, txn_row->new, row); | |
366 | i++; | |
367 | } else { | |
368 | if (uuid_is_zero(&datum->values[i].uuid)) { | |
369 | zero = true; | |
370 | } | |
371 | ovsdb_datum_remove_unsafe(datum, i, &column->type); | |
372 | } | |
373 | } | |
374 | } | |
375 | ||
376 | if (datum->n != orig_n) { | |
377 | bitmap_set1(txn_row->changed, column->index); | |
378 | ovsdb_datum_sort_assert(datum, column->type.key.type); | |
379 | if (datum->n < column->type.n_min) { | |
380 | const struct uuid *row_uuid = ovsdb_row_get_uuid(txn_row->new); | |
381 | if (zero && !txn_row->old) { | |
382 | return ovsdb_error( | |
383 | "constraint violation", | |
384 | "Weak reference column \"%s\" in \"%s\" row "UUID_FMT | |
385 | " (inserted within this transaction) contained " | |
386 | "all-zeros UUID (probably as the default value for " | |
387 | "this column) but deleting this value caused a " | |
388 | "constraint volation because this column is not " | |
389 | "allowed to be empty.", column->name, | |
390 | table->schema->name, UUID_ARGS(row_uuid)); | |
391 | } else { | |
392 | return ovsdb_error( | |
393 | "constraint violation", | |
394 | "Deletion of %u weak reference(s) to deleted (or " | |
395 | "never-existing) rows from column \"%s\" in \"%s\" " | |
396 | "row "UUID_FMT" %scaused this column to become empty, " | |
397 | "but constraints on this column disallow an " | |
398 | "empty column.", | |
399 | orig_n - datum->n, column->name, table->schema->name, | |
400 | UUID_ARGS(row_uuid), | |
401 | (txn_row->old | |
402 | ? "" | |
403 | : "(inserted within this transaction) ")); | |
404 | } | |
405 | } | |
406 | } | |
407 | } | |
408 | ||
409 | return NULL; | |
410 | } | |
411 | ||
17d18afb BP |
412 | static struct ovsdb_error * WARN_UNUSED_RESULT |
413 | determine_changes(struct ovsdb_txn *txn, struct ovsdb_txn_row *txn_row) | |
414 | { | |
415 | struct ovsdb_table *table; | |
416 | ||
417 | table = (txn_row->old ? txn_row->old : txn_row->new)->table; | |
418 | if (txn_row->old && txn_row->new) { | |
419 | struct shash_node *node; | |
420 | bool changed = false; | |
421 | ||
422 | SHASH_FOR_EACH (node, &table->schema->columns) { | |
423 | const struct ovsdb_column *column = node->data; | |
424 | const struct ovsdb_type *type = &column->type; | |
425 | unsigned int idx = column->index; | |
426 | ||
427 | if (!ovsdb_datum_equals(&txn_row->old->fields[idx], | |
428 | &txn_row->new->fields[idx], | |
429 | type)) { | |
430 | bitmap_set1(txn_row->changed, idx); | |
431 | changed = true; | |
432 | } | |
433 | } | |
434 | ||
435 | if (!changed) { | |
436 | /* Nothing actually changed in this row, so drop it. */ | |
437 | ovsdb_txn_row_abort(txn, txn_row); | |
438 | } | |
439 | } else { | |
440 | bitmap_set_multiple(txn_row->changed, 0, | |
441 | shash_count(&table->schema->columns), 1); | |
442 | } | |
443 | ||
444 | return NULL; | |
445 | } | |
446 | ||
87ab878c BP |
447 | static struct ovsdb_error * WARN_UNUSED_RESULT |
448 | check_max_rows(struct ovsdb_txn *txn) | |
449 | { | |
450 | struct ovsdb_txn_table *t; | |
451 | ||
4e8e4213 | 452 | LIST_FOR_EACH (t, node, &txn->txn_tables) { |
87ab878c BP |
453 | size_t n_rows = hmap_count(&t->table->rows); |
454 | unsigned int max_rows = t->table->schema->max_rows; | |
455 | ||
456 | if (n_rows > max_rows) { | |
457 | return ovsdb_error("constraint violation", | |
458 | "transaction causes \"%s\" table to contain " | |
459 | "%zu rows, greater than the schema-defined " | |
460 | "limit of %u row(s)", | |
461 | t->table->schema->name, n_rows, max_rows); | |
462 | } | |
463 | } | |
464 | ||
465 | return NULL; | |
466 | } | |
467 | ||
bd06962a BP |
468 | struct ovsdb_error * |
469 | ovsdb_txn_commit(struct ovsdb_txn *txn, bool durable) | |
f85f8ebb | 470 | { |
bd06962a BP |
471 | struct ovsdb_replica *replica; |
472 | struct ovsdb_error *error; | |
f85f8ebb | 473 | |
17d18afb BP |
474 | /* Figure out what actually changed, and abort early if the transaction |
475 | * was really a no-op. */ | |
476 | error = for_each_txn_row(txn, determine_changes); | |
477 | if (error) { | |
1dd5b71d | 478 | return OVSDB_WRAP_BUG("can't happen", error); |
17d18afb BP |
479 | } |
480 | if (list_is_empty(&txn->txn_tables)) { | |
481 | ovsdb_txn_abort(txn); | |
482 | return NULL; | |
483 | } | |
484 | ||
87ab878c BP |
485 | /* Check maximum rows table constraints. */ |
486 | error = check_max_rows(txn); | |
487 | if (error) { | |
488 | ovsdb_txn_abort(txn); | |
489 | return error; | |
490 | } | |
491 | ||
17d18afb | 492 | /* Update reference counts and check referential integrity. */ |
0d0f05b9 BP |
493 | error = update_ref_counts(txn); |
494 | if (error) { | |
495 | ovsdb_txn_abort(txn); | |
496 | return error; | |
497 | } | |
498 | ||
7360012b BP |
499 | /* Check reference counts and remove bad reference for "weak" referential |
500 | * integrity. */ | |
501 | error = for_each_txn_row(txn, assess_weak_refs); | |
502 | if (error) { | |
503 | ovsdb_txn_abort(txn); | |
504 | return error; | |
505 | } | |
506 | ||
17d18afb | 507 | /* Send the commit to each replica. */ |
4e8e4213 | 508 | LIST_FOR_EACH (replica, node, &txn->db->replicas) { |
bd06962a | 509 | error = (replica->class->commit)(replica, txn, durable); |
f85f8ebb | 510 | if (error) { |
bd06962a BP |
511 | /* We don't support two-phase commit so only the first replica is |
512 | * allowed to report an error. */ | |
513 | assert(&replica->node == txn->db->replicas.next); | |
f85f8ebb | 514 | |
bd06962a | 515 | ovsdb_txn_abort(txn); |
f85f8ebb BP |
516 | return error; |
517 | } | |
518 | } | |
519 | ||
3e010b7a | 520 | /* Finalize commit. */ |
bd06962a | 521 | txn->db->run_triggers = true; |
3e010b7a BP |
522 | ovsdb_error_assert(for_each_txn_row(txn, ovsdb_txn_row_commit)); |
523 | ovsdb_txn_free(txn); | |
524 | ||
f85f8ebb BP |
525 | return NULL; |
526 | } | |
527 | ||
bd06962a BP |
528 | void |
529 | ovsdb_txn_for_each_change(const struct ovsdb_txn *txn, | |
530 | ovsdb_txn_row_cb_func *cb, void *aux) | |
f85f8ebb | 531 | { |
bd06962a BP |
532 | struct ovsdb_txn_table *t; |
533 | struct ovsdb_txn_row *r; | |
f85f8ebb | 534 | |
4e8e4213 BP |
535 | LIST_FOR_EACH (t, node, &txn->txn_tables) { |
536 | HMAP_FOR_EACH (r, hmap_node, &t->txn_rows) { | |
17d18afb | 537 | if (!cb(r->old, r->new, r->changed, aux)) { |
bd06962a BP |
538 | break; |
539 | } | |
f85f8ebb | 540 | } |
bd06962a | 541 | } |
f85f8ebb BP |
542 | } |
543 | ||
544 | static struct ovsdb_txn_table * | |
71c93bd4 | 545 | ovsdb_txn_create_txn_table(struct ovsdb_txn *txn, struct ovsdb_table *table) |
f85f8ebb | 546 | { |
71c93bd4 BP |
547 | if (!table->txn_table) { |
548 | struct ovsdb_txn_table *txn_table; | |
f85f8ebb | 549 | |
71c93bd4 | 550 | table->txn_table = txn_table = xmalloc(sizeof *table->txn_table); |
f85f8ebb BP |
551 | txn_table->table = table; |
552 | hmap_init(&txn_table->txn_rows); | |
3e010b7a | 553 | txn_table->serial = serial - 1; |
71c93bd4 | 554 | list_push_back(&txn->txn_tables, &txn_table->node); |
f85f8ebb | 555 | } |
71c93bd4 | 556 | return table->txn_table; |
f85f8ebb BP |
557 | } |
558 | ||
559 | static struct ovsdb_txn_row * | |
71c93bd4 | 560 | ovsdb_txn_row_create(struct ovsdb_txn *txn, struct ovsdb_table *table, |
b405dcfb | 561 | const struct ovsdb_row *old_, struct ovsdb_row *new) |
f85f8ebb | 562 | { |
b405dcfb | 563 | struct ovsdb_row *old = (struct ovsdb_row *) old_; |
17d18afb | 564 | size_t n_columns = shash_count(&table->schema->columns); |
71c93bd4 | 565 | struct ovsdb_txn_table *txn_table; |
f85f8ebb BP |
566 | struct ovsdb_txn_row *txn_row; |
567 | ||
17d18afb BP |
568 | txn_row = xzalloc(offsetof(struct ovsdb_txn_row, changed) |
569 | + bitmap_n_bytes(n_columns)); | |
570 | txn_row->old = (struct ovsdb_row *) old; | |
f85f8ebb | 571 | txn_row->new = new; |
c7d85e0d | 572 | txn_row->n_refs = old ? old->n_refs : 0; |
3e010b7a | 573 | txn_row->serial = serial - 1; |
71c93bd4 | 574 | |
b405dcfb BP |
575 | if (old) { |
576 | old->txn_row = txn_row; | |
577 | } | |
578 | if (new) { | |
579 | new->txn_row = txn_row; | |
580 | } | |
581 | ||
71c93bd4 BP |
582 | txn_table = ovsdb_txn_create_txn_table(txn, table); |
583 | hmap_insert(&txn_table->txn_rows, &txn_row->hmap_node, | |
584 | ovsdb_row_hash(old ? old : new)); | |
f85f8ebb BP |
585 | |
586 | return txn_row; | |
587 | } | |
588 | ||
589 | struct ovsdb_row * | |
590 | ovsdb_txn_row_modify(struct ovsdb_txn *txn, const struct ovsdb_row *ro_row_) | |
591 | { | |
592 | struct ovsdb_row *ro_row = (struct ovsdb_row *) ro_row_; | |
593 | ||
594 | if (ro_row->txn_row) { | |
595 | assert(ro_row == ro_row->txn_row->new); | |
596 | return ro_row; | |
597 | } else { | |
598 | struct ovsdb_table *table = ro_row->table; | |
f85f8ebb BP |
599 | struct ovsdb_row *rw_row; |
600 | ||
f85f8ebb | 601 | rw_row = ovsdb_row_clone(ro_row); |
0d0f05b9 | 602 | rw_row->n_refs = ro_row->n_refs; |
f85f8ebb | 603 | uuid_generate(ovsdb_row_get_version_rw(rw_row)); |
b405dcfb | 604 | ovsdb_txn_row_create(txn, table, ro_row, rw_row); |
f85f8ebb BP |
605 | hmap_replace(&table->rows, &ro_row->hmap_node, &rw_row->hmap_node); |
606 | ||
607 | return rw_row; | |
608 | } | |
609 | } | |
610 | ||
611 | void | |
612 | ovsdb_txn_row_insert(struct ovsdb_txn *txn, struct ovsdb_row *row) | |
613 | { | |
614 | uint32_t hash = ovsdb_row_hash(row); | |
615 | struct ovsdb_table *table = row->table; | |
f85f8ebb BP |
616 | |
617 | uuid_generate(ovsdb_row_get_version_rw(row)); | |
618 | ||
b405dcfb | 619 | ovsdb_txn_row_create(txn, table, NULL, row); |
f85f8ebb BP |
620 | hmap_insert(&table->rows, &row->hmap_node, hash); |
621 | } | |
622 | ||
623 | /* 'row' must be assumed destroyed upon return; the caller must not reference | |
624 | * it again. */ | |
625 | void | |
626 | ovsdb_txn_row_delete(struct ovsdb_txn *txn, const struct ovsdb_row *row_) | |
627 | { | |
628 | struct ovsdb_row *row = (struct ovsdb_row *) row_; | |
629 | struct ovsdb_table *table = row->table; | |
630 | struct ovsdb_txn_row *txn_row = row->txn_row; | |
f85f8ebb BP |
631 | |
632 | hmap_remove(&table->rows, &row->hmap_node); | |
633 | ||
634 | if (!txn_row) { | |
b405dcfb | 635 | ovsdb_txn_row_create(txn, table, row, NULL); |
f85f8ebb BP |
636 | } else { |
637 | assert(txn_row->new == row); | |
638 | if (txn_row->old) { | |
639 | txn_row->new = NULL; | |
640 | } else { | |
71c93bd4 | 641 | hmap_remove(&table->txn_table->txn_rows, &txn_row->hmap_node); |
e084f690 | 642 | free(txn_row); |
f85f8ebb BP |
643 | } |
644 | ovsdb_row_destroy(row); | |
645 | } | |
646 | } | |
d171b584 BP |
647 | |
648 | void | |
649 | ovsdb_txn_add_comment(struct ovsdb_txn *txn, const char *s) | |
650 | { | |
651 | if (txn->comment.length) { | |
652 | ds_put_char(&txn->comment, '\n'); | |
653 | } | |
654 | ds_put_cstr(&txn->comment, s); | |
655 | } | |
656 | ||
657 | const char * | |
658 | ovsdb_txn_get_comment(const struct ovsdb_txn *txn) | |
659 | { | |
660 | return txn->comment.length ? ds_cstr_ro(&txn->comment) : NULL; | |
661 | } | |
3e010b7a BP |
662 | \f |
663 | static void | |
664 | ovsdb_txn_row_prefree(struct ovsdb_txn_row *txn_row) | |
665 | { | |
666 | struct ovsdb_row *row = txn_row->old ? txn_row->old : txn_row->new; | |
667 | struct ovsdb_txn_table *txn_table = row->table->txn_table; | |
668 | ||
669 | txn_table->n_processed--; | |
670 | hmap_remove(&txn_table->txn_rows, &txn_row->hmap_node); | |
671 | ||
672 | if (txn_row->old) { | |
673 | txn_row->old->txn_row = NULL; | |
674 | } | |
675 | if (txn_row->new) { | |
676 | txn_row->new->txn_row = NULL; | |
677 | } | |
678 | } | |
679 | ||
680 | static void | |
681 | ovsdb_txn_table_destroy(struct ovsdb_txn_table *txn_table) | |
682 | { | |
683 | assert(hmap_is_empty(&txn_table->txn_rows)); | |
684 | txn_table->table->txn_table = NULL; | |
685 | hmap_destroy(&txn_table->txn_rows); | |
686 | list_remove(&txn_table->node); | |
687 | free(txn_table); | |
688 | } | |
689 | ||
690 | /* Calls 'cb' for every txn_row within 'txn'. If 'cb' returns nonnull, this | |
691 | * aborts the iteration and for_each_txn_row() passes the error up. Otherwise, | |
692 | * returns a null pointer after iteration is complete. | |
693 | * | |
694 | * 'cb' may insert new txn_rows and new txn_tables into 'txn'. It may delete | |
695 | * the txn_row that it is passed in, or txn_rows in txn_tables other than the | |
696 | * one passed to 'cb'. It may *not* delete txn_rows other than the one passed | |
697 | * in within the same txn_table. It may *not* delete any txn_tables. As long | |
698 | * as these rules are followed, 'cb' will be called exactly once for each | |
699 | * txn_row in 'txn', even those added by 'cb'. | |
700 | */ | |
701 | static struct ovsdb_error * WARN_UNUSED_RESULT | |
702 | for_each_txn_row(struct ovsdb_txn *txn, | |
703 | struct ovsdb_error *(*cb)(struct ovsdb_txn *, | |
704 | struct ovsdb_txn_row *)) | |
705 | { | |
706 | bool any_work; | |
707 | ||
708 | serial++; | |
709 | ||
710 | do { | |
711 | struct ovsdb_txn_table *t, *next_txn_table; | |
712 | ||
713 | any_work = false; | |
4e8e4213 | 714 | LIST_FOR_EACH_SAFE (t, next_txn_table, node, &txn->txn_tables) { |
3e010b7a BP |
715 | if (t->serial != serial) { |
716 | t->serial = serial; | |
717 | t->n_processed = 0; | |
718 | } | |
719 | ||
720 | while (t->n_processed < hmap_count(&t->txn_rows)) { | |
721 | struct ovsdb_txn_row *r, *next_txn_row; | |
722 | ||
4e8e4213 | 723 | HMAP_FOR_EACH_SAFE (r, next_txn_row, hmap_node, &t->txn_rows) { |
3e010b7a BP |
724 | if (r->serial != serial) { |
725 | struct ovsdb_error *error; | |
726 | ||
727 | r->serial = serial; | |
728 | t->n_processed++; | |
729 | any_work = true; | |
730 | ||
731 | error = cb(txn, r); | |
732 | if (error) { | |
733 | return error; | |
734 | } | |
735 | } | |
736 | } | |
737 | } | |
738 | if (hmap_is_empty(&t->txn_rows)) { | |
739 | /* Table is empty. Drop it. */ | |
740 | ovsdb_txn_table_destroy(t); | |
741 | } | |
742 | } | |
743 | } while (any_work); | |
744 | ||
745 | return NULL; | |
746 | } |