ovsdb: Add new "mutation" operation to transactions.
authorBen Pfaff <blp@nicira.com>
Wed, 16 Dec 2009 18:49:31 +0000 (10:49 -0800)
committerBen Pfaff <blp@nicira.com>
Wed, 16 Dec 2009 18:56:04 +0000 (10:56 -0800)
13 files changed:
lib/ovsdb-data.c
lib/ovsdb-data.h
ovsdb/SPECS
ovsdb/automake.mk
ovsdb/condition.c
ovsdb/execution.c
ovsdb/mutation.c [new file with mode: 0644]
ovsdb/mutation.h [new file with mode: 0644]
tests/automake.mk
tests/ovsdb-execution.at
tests/ovsdb-mutation.at [new file with mode: 0644]
tests/ovsdb.at
tests/test-ovsdb.c

index df445d775c12d5eb91302286ef4506354b123189..0d7749b17fc3912606128384c781d250e32212ee 100644 (file)
@@ -18,6 +18,7 @@
 #include "ovsdb-data.h"
 
 #include <assert.h>
+#include <limits.h>
 
 #include "hash.h"
 #include "ovsdb-error.h"
@@ -433,7 +434,7 @@ ovsdb_datum_sort_swap_cb(size_t a, size_t b, void *cbdata_)
     }
 }
 
-static struct ovsdb_error *
+struct ovsdb_error *
 ovsdb_datum_sort(struct ovsdb_datum *datum, const struct ovsdb_type *type)
 {
     if (datum->n < 2) {
@@ -654,27 +655,33 @@ ovsdb_datum_compare_3way(const struct ovsdb_datum *a,
                                        a->n));
 }
 
-static bool
-ovsdb_datum_contains(const struct ovsdb_datum *a, int i,
-                     const struct ovsdb_datum *b,
-                     const struct ovsdb_type *type)
+/* If atom 'i' in 'a' is also in 'b', returns its index in 'b', otherwise
+ * UINT_MAX.  'type' must be the type of 'a' and 'b', except that
+ * type->value_type may be set to OVSDB_TYPE_VOID to compare keys but not
+ * values. */
+static unsigned int
+ovsdb_datum_find(const struct ovsdb_datum *a, int i,
+                 const struct ovsdb_datum *b,
+                 const struct ovsdb_type *type)
 {
     int low = 0;
     int high = b->n;
     while (low < high) {
         int j = (low + high) / 2;
-        int cmp = ovsdb_atom_compare_3way(&a->keys[i], &b->keys[j], type->key_type);
+        int cmp = ovsdb_atom_compare_3way(&a->keys[i], &b->keys[j],
+                                          type->key_type);
         if (cmp < 0) {
             high = j;
         } else if (cmp > 0) {
             low = j + 1;
         } else {
-            return (type->value_type == OVSDB_TYPE_VOID
-                    || ovsdb_atom_equals(&a->values[i], &b->values[j],
-                                         type->value_type));
+            bool eq_value = (type->value_type == OVSDB_TYPE_VOID
+                             || ovsdb_atom_equals(&a->values[i], &b->values[j],
+                                                  type->value_type));
+            return eq_value ? j : UINT_MAX;
         }
     }
-    return false;
+    return UINT_MAX;
 }
 
 /* Returns true if every element in 'a' is also in 'b', false otherwise. */
@@ -686,7 +693,7 @@ ovsdb_datum_includes_all(const struct ovsdb_datum *a,
     size_t i;
 
     for (i = 0; i < a->n; i++) {
-        if (!ovsdb_datum_contains(a, i, b, type)) {
+        if (ovsdb_datum_find(a, i, b, type) == UINT_MAX) {
             return false;
         }
     }
@@ -702,12 +709,95 @@ ovsdb_datum_excludes_all(const struct ovsdb_datum *a,
     size_t i;
 
     for (i = 0; i < a->n; i++) {
-        if (ovsdb_datum_contains(a, i, b, type)) {
+        if (ovsdb_datum_find(a, i, b, type) != UINT_MAX) {
             return false;
         }
     }
     return true;
 }
+
+static void
+ovsdb_datum_reallocate(struct ovsdb_datum *a, const struct ovsdb_type *type,
+                       unsigned int capacity)
+{
+    a->keys = xrealloc(a->keys, capacity * sizeof *a->keys);
+    if (type->value_type != OVSDB_TYPE_VOID) {
+        a->values = xrealloc(a->values, capacity * sizeof *a->values);
+    }
+}
+
+static void
+ovsdb_datum_remove(struct ovsdb_datum *a, size_t i,
+                   const struct ovsdb_type *type)
+{
+    ovsdb_atom_destroy(&a->keys[i], type->key_type);
+    a->keys[i] = a->keys[a->n - 1];
+    if (type->value_type != OVSDB_TYPE_VOID) {
+        ovsdb_atom_destroy(&a->values[i], type->value_type);
+        a->values[i] = a->values[a->n - 1];
+    }
+    a->n--;
+}
+
+void
+ovsdb_datum_union(struct ovsdb_datum *a,
+                  const struct ovsdb_datum *b, const struct ovsdb_type *type)
+{
+    struct ovsdb_type type_without_value;
+    unsigned int n;
+    size_t i;
+
+    type_without_value = *type;
+    type_without_value.value_type = OVSDB_TYPE_VOID;
+    n = a->n;
+    for (i = 0; i < b->n; i++) {
+        if (ovsdb_datum_find(b, i, a, &type_without_value) == UINT_MAX) {
+            if (n == a->n) {
+                ovsdb_datum_reallocate(a, type, a->n + (b->n - i));
+            }
+            ovsdb_atom_clone(&a->keys[n], &b->keys[i], type->key_type);
+            if (type->value_type != OVSDB_TYPE_VOID) {
+                ovsdb_atom_clone(&a->values[n], &b->values[i],
+                                 type->value_type);
+            }
+            n++;
+        }
+    }
+    if (n != a->n) {
+        struct ovsdb_error *error;
+        a->n = n;
+        error = ovsdb_datum_sort(a, type);
+        assert(!error);
+    }
+}
+
+void
+ovsdb_datum_subtract(struct ovsdb_datum *a, const struct ovsdb_type *a_type,
+                     const struct ovsdb_datum *b,
+                     const struct ovsdb_type *b_type)
+{
+    bool changed = false;
+    size_t i;
+
+    assert(a_type->key_type == b_type->key_type);
+    assert(a_type->value_type == b_type->value_type
+           || b_type->value_type == OVSDB_TYPE_VOID);
+
+    /* XXX The big-O of this could easily be improved. */
+    for (i = 0; i < a->n; ) {
+        unsigned int idx = ovsdb_datum_find(a, i, b, b_type);
+        if (idx != UINT_MAX) {
+            changed = true;
+            ovsdb_datum_remove(a, i, a_type);
+        } else {
+            i++;
+        }
+    }
+    if (changed) {
+        struct ovsdb_error *error = ovsdb_datum_sort(a, a_type);
+        assert(!error);
+    }
+}
 \f
 struct ovsdb_symbol_table {
     struct shash sh;
index b31aa5d496f07d10ed0cdd0af2ba3f2f58787d31..3f2d489437e5a42d067626a273382a11eae13029 100644 (file)
@@ -84,6 +84,8 @@ void ovsdb_datum_clone(struct ovsdb_datum *, const struct ovsdb_datum *,
                        const struct ovsdb_type *);
 void ovsdb_datum_destroy(struct ovsdb_datum *, const struct ovsdb_type *);
 void ovsdb_datum_swap(struct ovsdb_datum *, struct ovsdb_datum *);
+struct ovsdb_error *ovsdb_datum_sort(struct ovsdb_datum *,
+                                     const struct ovsdb_type *);
 
 struct ovsdb_error *ovsdb_datum_from_json(struct ovsdb_datum *,
                                           const struct ovsdb_type *,
@@ -108,6 +110,14 @@ bool ovsdb_datum_excludes_all(const struct ovsdb_datum *,
                               const struct ovsdb_datum *,
                               const struct ovsdb_type *);
 
+void ovsdb_datum_union(struct ovsdb_datum *,
+                       const struct ovsdb_datum *,
+                       const struct ovsdb_type *);
+void ovsdb_datum_subtract(struct ovsdb_datum *a,
+                          const struct ovsdb_type *a_type,
+                          const struct ovsdb_datum *b,
+                          const struct ovsdb_type *b_type);
+
 static inline bool
 ovsdb_datum_conforms_to_type(const struct ovsdb_datum *datum,
                              const struct ovsdb_type *type)
index 93db15d4cc41191c75af22688ff69cef252db0a4..9fd654be6d1c51857ed0d5932adf47bfd6a4440b 100644 (file)
@@ -586,6 +586,79 @@ Notation for the Wire Protocol
 
     One of "<", "<=", "==", "!=", ">=", ">", "includes", "excludes".
 
+<mutation>
+
+    A 3-element JSON array of the form [<column>, <mutator>, <value>]
+    that represents a change to a column value.
+
+    Except as otherwise specified below, <value> must have the same
+    type as <column>.
+
+    The meaning depends on the type of <column>:
+
+        integer
+        real
+
+            <mutator> must be "+=", "-=", "*=", "/=" or (integer only)
+            "%=".  The value of <column> is changed to the sum,
+            difference, product, quotient, or remainder, respectively,
+            of <column> and <value>.
+
+        boolean
+        string
+        uuid
+
+            No valid <mutator>s are currently defined for these types.
+
+        set
+
+            Any <mutator> valid for the set's element type may be
+            applied to the set, in which case the mutation is applied
+            to each member of the set individually.  <value> must be a
+            scalar value of the same type as the set's element type.
+
+            If <mutator> is "insert", then each of the values in the
+            set in <value> is added to <column> if it is not already
+            present.  The required type of <value> is slightly
+            relaxed, in that it may have fewer than the minimum number
+            of elements specified by the column's type.
+
+            If <mutator> is "delete", then each of the values in the
+            set in <value> is removed from <column> if it is present
+            there.  The required type is slightly relaxed in that
+            <value> may have more or less than the maximum number of
+            elements specified by the column's type.
+
+        map
+
+            <mutator> must be "insert" or "delete".
+
+            If <mutator> is "insert", then each of the key-value pairs
+            in the map in <value> is added to <column> if its key is
+            not already present.  The required type of <value> is
+            slightly relaxed, in that it may have fewer than the
+            minimum number of elements specified by the column's type.
+
+            If <mutator> is "delete", then <value> may have the same
+            type as <column> (a map type) or it may be a set whose
+            element type is the same as <column>'s key type:
+
+                - If <value> is a map, the mutation deletes each
+                  key-value pair in <column> whose key and value equal
+                  one of the key-value pairs in <value>.
+
+                - If <value> is a set, the mutation deletes each
+                  key-value pair in <column> whose key equals one of
+                  the values in <value>.
+
+            For "delete", <value> may have any number of elements,
+            regardless of restrictions on the number of elements in
+            <column>.
+
+<mutator>
+
+    One of "+=", "-=", "*=", "/=", "%=", "insert", "delete".
+
 Operations
 ----------
 
@@ -699,6 +772,57 @@ Semantics:
     The "count" member of the result specifies the number of rows
     that matched.
 
+mutate
+......
+
+Request object members:
+
+    "op": "mutate"                required
+    "table": <table>              required
+    "where": [<condition>*]       required
+    "mutations": [<mutation>*]    required
+
+Result object members:
+
+    "count": <integer>
+
+Semantics:
+
+    Mutates rows in a table.
+
+    Searches "table" for rows that match all the conditions specified
+    in "where".  For each matching row, mutates its columns as
+    specified by each <mutation> in "mutations", in the order
+    specified.
+
+    The "_uuid" and "_version" columns of a table may not be directly
+    modified with this operation.  Columns designated read-only in the
+    schema also may not be updated.
+
+    The "count" member of the result specifies the number of rows
+    that matched.
+
+Errors:
+
+    "error": "domain error"
+
+        The result of the mutation is not mathematically defined,
+        e.g. division by zero.
+
+    "error": "range error"
+
+        The result of the mutation is not representable within the
+        database's format, e.g. an integer result outside the range
+        INT64_MIN...INT64_MAX or a real result outside the range
+        -DBL_MAX...DBL_MAX.
+
+    "error": "constraint violation"
+
+        The mutation caused the column's value to violate a
+        constraint, e.g. it caused a column to have more or fewer
+        values than are allowed or an arithmetic operation caused a
+        set or map to have duplicate elements.
+
 delete
 ......
 
index f3f5f9b9bfd6b8faf985577ff056508b5ea0a459..2732c53908ffb6c235a6d44d465be4c696c4beac 100644 (file)
@@ -12,6 +12,8 @@ ovsdb_libovsdb_a_SOURCES = \
        ovsdb/jsonrpc-server.h \
        ovsdb/log.c \
        ovsdb/log.h \
+       ovsdb/mutation.c \
+       ovsdb/mutation.h \
        ovsdb/ovsdb-server.c \
        ovsdb/ovsdb.c \
        ovsdb/ovsdb.h \
index 0342b8e8906920d2334921a141a7d4cebbb3b15f..f3f43003a232fe317388a2ce650e3cbaf763aaba 100644 (file)
@@ -52,7 +52,6 @@ ovsdb_function_to_string(enum ovsdb_function function)
     return NULL;
 }
 
-
 static WARN_UNUSED_RESULT struct ovsdb_error *
 ovsdb_clause_from_json(const struct ovsdb_table_schema *ts,
                        const struct json *json,
index 932bee2380bfe3b4853bb775bf583399f25186f3..67f6b8d0611ab213b86980217d55ff30ef16f437 100644 (file)
@@ -22,6 +22,7 @@
 #include "condition.h"
 #include "file.h"
 #include "json.h"
+#include "mutation.h"
 #include "ovsdb-data.h"
 #include "ovsdb-error.h"
 #include "ovsdb-parser.h"
@@ -50,6 +51,7 @@ typedef struct ovsdb_error *ovsdb_operation_executor(struct ovsdb_execution *,
 static ovsdb_operation_executor ovsdb_execute_insert;
 static ovsdb_operation_executor ovsdb_execute_select;
 static ovsdb_operation_executor ovsdb_execute_update;
+static ovsdb_operation_executor ovsdb_execute_mutate;
 static ovsdb_operation_executor ovsdb_execute_delete;
 static ovsdb_operation_executor ovsdb_execute_wait;
 static ovsdb_operation_executor ovsdb_execute_commit;
@@ -68,6 +70,7 @@ lookup_executor(const char *name)
         { "insert", ovsdb_execute_insert },
         { "select", ovsdb_execute_select },
         { "update", ovsdb_execute_update },
+        { "mutate", ovsdb_execute_mutate },
         { "delete", ovsdb_execute_delete },
         { "wait", ovsdb_execute_wait },
         { "commit", ovsdb_execute_commit },
@@ -414,6 +417,64 @@ ovsdb_execute_update(struct ovsdb_execution *x, struct ovsdb_parser *parser,
     return error;
 }
 
+struct mutate_row_cbdata {
+    size_t n_matches;
+    struct ovsdb_txn *txn;
+    const struct ovsdb_mutation_set *mutations;
+};
+
+static bool
+mutate_row_cb(const struct ovsdb_row *row, void *mr_)
+{
+    struct mutate_row_cbdata *mr = mr_;
+
+    mr->n_matches++;
+    ovsdb_mutation_set_execute(ovsdb_txn_row_modify(mr->txn, row),
+                               mr->mutations);
+
+    return true;
+}
+
+struct ovsdb_error *
+ovsdb_execute_mutate(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result)
+{
+    struct ovsdb_table *table;
+    const struct json *where;
+    const struct json *mutations_json;
+    struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+    struct ovsdb_mutation_set mutations = OVSDB_MUTATION_SET_INITIALIZER;
+    struct ovsdb_row *row = NULL;
+    struct mutate_row_cbdata mr;
+    struct ovsdb_error *error;
+
+    table = parse_table(x, parser, "table");
+    where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+    mutations_json = ovsdb_parser_member(parser, "mutations", OP_ARRAY);
+    error = ovsdb_parser_get_error(parser);
+    if (!error) {
+        error = ovsdb_mutation_set_from_json(table->schema, mutations_json,
+                                             x->symtab, &mutations);
+    }
+    if (!error) {
+        error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+                                          &condition);
+    }
+    if (!error) {
+        mr.n_matches = 0;
+        mr.txn = x->txn;
+        mr.mutations = &mutations;
+        ovsdb_query(table, &condition, mutate_row_cb, &mr);
+        json_object_put(result, "count", json_integer_create(mr.n_matches));
+    }
+
+    ovsdb_row_destroy(row);
+    ovsdb_mutation_set_destroy(&mutations);
+    ovsdb_condition_destroy(&condition);
+
+    return error;
+}
+
 struct delete_row_cbdata {
     size_t n_matches;
     const struct ovsdb_table *table;
diff --git a/ovsdb/mutation.c b/ovsdb/mutation.c
new file mode 100644 (file)
index 0000000..ba56827
--- /dev/null
@@ -0,0 +1,462 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "mutation.h"
+
+#include <float.h>
+#include <limits.h>
+
+#include "column.h"
+#include "ovsdb-error.h"
+#include "json.h"
+#include "row.h"
+#include "table.h"
+
+enum mutate_error {
+    ME_OK,
+    ME_DOM,
+    ME_RANGE,
+    ME_COUNT,
+    ME_DUP
+};
+
+struct ovsdb_error *
+ovsdb_mutator_from_string(const char *name, enum ovsdb_mutator *mutator)
+{
+#define OVSDB_MUTATOR(ENUM, NAME)               \
+    if (!strcmp(name, NAME)) {                  \
+        *mutator = ENUM;                        \
+        return NULL;                            \
+    }
+    OVSDB_MUTATORS;
+#undef OVSDB_MUTATOR
+
+    return ovsdb_syntax_error(NULL, "unknown mutator",
+                              "No mutator named %s.", name);
+}
+
+const char *
+ovsdb_mutator_to_string(enum ovsdb_mutator mutator)
+{
+    switch (mutator) {
+#define OVSDB_MUTATOR(ENUM, NAME) case ENUM: return NAME;
+        OVSDB_MUTATORS;
+#undef OVSDB_MUTATOR
+    }
+
+    return NULL;
+}
+
+static WARN_UNUSED_RESULT struct ovsdb_error *
+type_mismatch(const struct ovsdb_mutation *m, const struct json *json)
+{
+    struct ovsdb_error *error;
+    char *s;
+
+    s = ovsdb_type_to_english(&m->column->type);
+    error = ovsdb_syntax_error(
+        json, NULL, "Type mismatch: \"%s\" operator may not be "
+        "applied to column %s of type %s.",
+        ovsdb_mutator_to_string(m->mutator), m->column->name, s);
+    free(s);
+
+    return error;
+}
+
+static WARN_UNUSED_RESULT struct ovsdb_error *
+ovsdb_mutation_from_json(const struct ovsdb_table_schema *ts,
+                         const struct json *json,
+                         const struct ovsdb_symbol_table *symtab,
+                         struct ovsdb_mutation *m)
+{
+    const struct json_array *array;
+    struct ovsdb_error *error;
+    const char *mutator_name;
+    const char *column_name;
+
+    if (json->type != JSON_ARRAY
+        || json->u.array.n != 3
+        || json->u.array.elems[0]->type != JSON_STRING
+        || json->u.array.elems[1]->type != JSON_STRING) {
+        return ovsdb_syntax_error(json, NULL, "Parse error in mutation.");
+    }
+    array = json_array(json);
+
+    column_name = json_string(array->elems[0]);
+    m->column = ovsdb_table_schema_get_column(ts, column_name);
+    if (!m->column) {
+        return ovsdb_syntax_error(json, "unknown column",
+                                  "No column %s in table %s.",
+                                  column_name, ts->name);
+    }
+    m->type = m->column->type;
+
+    mutator_name = json_string(array->elems[1]);
+    error = ovsdb_mutator_from_string(mutator_name, &m->mutator);
+    if (error) {
+        return error;
+    }
+
+    /* Type-check and relax restrictions on 'type' if appropriate.  */
+    switch (m->mutator) {
+    case OVSDB_M_ADD:
+    case OVSDB_M_SUB:
+    case OVSDB_M_MUL:
+    case OVSDB_M_DIV:
+    case OVSDB_M_MOD:
+        if ((!ovsdb_type_is_scalar(&m->type) && !ovsdb_type_is_set(&m->type))
+            || (m->type.key_type != OVSDB_TYPE_INTEGER
+                && m->type.key_type != OVSDB_TYPE_REAL)
+            || (m->mutator == OVSDB_M_MOD
+                && m->type.key_type == OVSDB_TYPE_REAL)) {
+            return type_mismatch(m, json);
+        }
+        m->type.n_min = m->type.n_max = 1;
+        return ovsdb_datum_from_json(&m->arg, &m->type, array->elems[2],
+                                     symtab);
+
+    case OVSDB_M_INSERT:
+    case OVSDB_M_DELETE:
+        if (!ovsdb_type_is_set(&m->type) && !ovsdb_type_is_map(&m->type)) {
+            return type_mismatch(m, json);
+        }
+        m->type.n_min = 0;
+        if (m->mutator == OVSDB_M_DELETE) {
+            m->type.n_max = UINT_MAX;
+        }
+        error = ovsdb_datum_from_json(&m->arg, &m->type, array->elems[2],
+                                      symtab);
+        if (error && ovsdb_type_is_map(&m->type)
+            && m->mutator == OVSDB_M_DELETE) {
+            ovsdb_error_destroy(error);
+            m->type.value_type = OVSDB_TYPE_VOID;
+            error = ovsdb_datum_from_json(&m->arg, &m->type, array->elems[2],
+                                          symtab);
+        }
+        return error;
+    }
+
+    NOT_REACHED();
+}
+
+static void
+ovsdb_mutation_free(struct ovsdb_mutation *m)
+{
+    ovsdb_datum_destroy(&m->arg, &m->type);
+}
+
+struct ovsdb_error *
+ovsdb_mutation_set_from_json(const struct ovsdb_table_schema *ts,
+                             const struct json *json,
+                             const struct ovsdb_symbol_table *symtab,
+                             struct ovsdb_mutation_set *set)
+{
+    const struct json_array *array = json_array(json);
+    size_t i;
+
+    set->mutations = xmalloc(array->n * sizeof *set->mutations);
+    set->n_mutations = 0;
+    for (i = 0; i < array->n; i++) {
+        struct ovsdb_error *error;
+        error = ovsdb_mutation_from_json(ts, array->elems[i], symtab,
+                                         &set->mutations[i]);
+        if (error) {
+            ovsdb_mutation_set_destroy(set);
+            set->mutations = NULL;
+            set->n_mutations = 0;
+            return error;
+        }
+        set->n_mutations++;
+    }
+
+    return NULL;
+}
+
+static struct json *
+ovsdb_mutation_to_json(const struct ovsdb_mutation *m)
+{
+    return json_array_create_3(
+        json_string_create(m->column->name),
+        json_string_create(ovsdb_mutator_to_string(m->mutator)),
+        ovsdb_datum_to_json(&m->arg, &m->type));
+}
+
+struct json *
+ovsdb_mutation_set_to_json(const struct ovsdb_mutation_set *set)
+{
+    struct json **mutations;
+    size_t i;
+
+    mutations = xmalloc(set->n_mutations * sizeof *mutations);
+    for (i = 0; i < set->n_mutations; i++) {
+        mutations[i] = ovsdb_mutation_to_json(&set->mutations[i]);
+    }
+    return json_array_create(mutations, set->n_mutations);
+}
+
+void
+ovsdb_mutation_set_destroy(struct ovsdb_mutation_set *set)
+{
+    size_t i;
+
+    for (i = 0; i < set->n_mutations; i++) {
+        ovsdb_mutation_free(&set->mutations[i]);
+    }
+    free(set->mutations);
+}
+
+static int
+add_int(int64_t *x, int64_t y)
+{
+    /* Check for overflow.  See _Hacker's Delight_ pp. 27. */
+    int64_t z = ~(*x ^ y) & INT64_MIN;
+    if ((~(*x ^ y) & ~(((*x ^ z) + y) ^ y)) >> 63) {
+        return ME_RANGE;
+    } else {
+        *x += y;
+        return 0;
+    }
+}
+
+static int
+sub_int(int64_t *x, int64_t y)
+{
+    /* Check for overflow.  See _Hacker's Delight_ pp. 27. */
+    int64_t z = (*x ^ y) & INT64_MIN;
+    if (((*x ^ y) & (((*x ^ z) - y) ^ y)) >> 63) {
+        return ME_RANGE;
+    } else {
+        *x -= y;
+        return 0;
+    }
+}
+
+static int
+mul_int(int64_t *x, int64_t y)
+{
+    /* Check for overflow.  See _Hacker's Delight_ pp. 30. */
+    if (*x > 0
+        ? (y > 0
+           ? *x >= INT64_MAX / y
+           : y  < INT64_MIN / *x)
+        : (y > 0
+           ? *x < INT64_MIN / y
+           : *x != 0 && y < INT64_MAX / y)) {
+        return ME_RANGE;
+    } else {
+        *x *= y;
+        return 0;
+    }
+}
+
+static int
+check_int_div(int64_t x, int64_t y)
+{
+    /* Check for overflow.  See _Hacker's Delight_ pp. 32. */
+    if (!y) {
+        return ME_DOM;
+    } else if (x == INT64_MIN && y == -1) {
+        return ME_RANGE;
+    } else {
+        return 0;
+    }
+}
+
+static int
+div_int(int64_t *x, int64_t y)
+{
+    int error = check_int_div(*x, y);
+    if (!error) {
+        *x /= y;
+    }
+    return error;
+}
+
+static int
+mod_int(int64_t *x, int64_t y)
+{
+    int error = check_int_div(*x, y);
+    if (!error) {
+        *x %= y;
+    }
+    return error;
+}
+
+static int
+check_real_range(double x)
+{
+    return x >= -DBL_MAX && x <= DBL_MAX ? 0 : ME_RANGE;
+}
+
+static int
+add_double(double *x, double y)
+{
+    *x += y;
+    return 0;
+}
+
+static int
+sub_double(double *x, double y)
+{
+    *x -= y;
+    return 0;
+}
+
+static int
+mul_double(double *x, double y)
+{
+    *x *= y;
+    return 0;
+}
+
+static int
+div_double(double *x, double y)
+{
+    if (y == 0) {
+        return ME_DOM;
+    } else {
+        *x /= y;
+        return 0;
+    }
+}
+
+static int
+mutate_scalar(const struct ovsdb_type *dst_type, struct ovsdb_datum *dst,
+              const union ovsdb_atom *arg,
+              int (*mutate_integer)(int64_t *x, int64_t y),
+              int (*mutate_real)(double *x, double y))
+{
+    struct ovsdb_error *error;
+    unsigned int i;
+
+    if (dst_type->key_type == OVSDB_TYPE_INTEGER) {
+        int64_t y = arg->integer;
+        for (i = 0; i < dst->n; i++) {
+            int error = mutate_integer(&dst->keys[i].integer, y);
+            if (error) {
+                return error;
+            }
+        }
+    } else if (dst_type->key_type == OVSDB_TYPE_REAL) {
+        double y = arg->real;
+        for (i = 0; i < dst->n; i++) {
+            double *x = &dst->keys[i].real;
+            int error = mutate_real(x, y);
+            if (!error) {
+                error = check_real_range(*x);
+            }
+            if (error) {
+                return error;
+            }
+        }
+    } else {
+        NOT_REACHED();
+    }
+
+    error = ovsdb_datum_sort(dst, dst_type);
+    if (error) {
+        ovsdb_error_destroy(error);
+        return ME_DUP;
+    }
+    return 0;
+}
+
+struct ovsdb_error *
+ovsdb_mutation_set_execute(struct ovsdb_row *row,
+                           const struct ovsdb_mutation_set *set)
+{
+    size_t i;
+
+    for (i = 0; i < set->n_mutations; i++) {
+        const struct ovsdb_mutation *m = &set->mutations[i];
+        struct ovsdb_datum *dst = &row->fields[m->column->index];
+        const struct ovsdb_type *dst_type = &m->column->type;
+        const struct ovsdb_datum *arg = &set->mutations[i].arg;
+        const struct ovsdb_type *arg_type = &m->type;
+        int error;
+
+        switch (m->mutator) {
+        case OVSDB_M_ADD:
+            error = mutate_scalar(dst_type, dst, &arg->keys[0],
+                                  add_int, add_double);
+            break;
+
+        case OVSDB_M_SUB:
+            error = mutate_scalar(dst_type, dst, &arg->keys[0],
+                                  sub_int, sub_double);
+            break;
+
+        case OVSDB_M_MUL:
+            error = mutate_scalar(dst_type, dst, &arg->keys[0],
+                                  mul_int, mul_double);
+            break;
+
+        case OVSDB_M_DIV:
+            error = mutate_scalar(dst_type, dst, &arg->keys[0],
+                                  div_int, div_double);
+            break;
+
+        case OVSDB_M_MOD:
+            error = mutate_scalar(dst_type, dst, &arg->keys[0],
+                                  mod_int, NULL);
+            break;
+
+        case OVSDB_M_INSERT:
+            ovsdb_datum_union(dst, arg, dst_type);
+            error = ovsdb_datum_conforms_to_type(dst, dst_type) ? 0 : ME_COUNT;
+            break;
+
+        case OVSDB_M_DELETE:
+            ovsdb_datum_subtract(dst, dst_type, arg, arg_type);
+            error = ovsdb_datum_conforms_to_type(dst, dst_type) ? 0 : ME_COUNT;
+            break;
+        }
+
+        switch (error) {
+        case 0:
+            break;
+
+        case ME_DOM:
+            return ovsdb_error("domain error", "Division by zero.");
+
+        case ME_RANGE:
+            return ovsdb_error("range error",
+                               "Result of \"%s\" operation is out of range.",
+                               ovsdb_mutator_to_string(m->mutator));
+
+        case ME_DUP:
+            return ovsdb_error("constraint violation",
+                               "Result of \"%s\" operation contains "
+                               "duplicates.",
+                               ovsdb_mutator_to_string(m->mutator));
+
+        case ME_COUNT: {
+            char *s = ovsdb_type_to_english(dst_type);
+            struct ovsdb_error *e = ovsdb_error(
+                "constaint violation",
+                "Attempted to store %u elements in %s.", dst->n, s);
+            free(s);
+            return e;
+        }
+
+        default:
+            return OVSDB_BUG("unexpected errno");
+        }
+    }
+
+    return NULL;
+}
diff --git a/ovsdb/mutation.h b/ovsdb/mutation.h
new file mode 100644 (file)
index 0000000..d466e28
--- /dev/null
@@ -0,0 +1,72 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_MUTATION_H
+#define OVSDB_MUTATION_H 1
+
+#include <stddef.h>
+#include "compiler.h"
+#include "ovsdb-data.h"
+
+struct json;
+struct ovsdb_table_schema;
+struct ovsdb_row;
+
+/* These list is ordered in ascending order of the fraction of tables row that
+ * they are (heuristically) expected to leave in query results. */
+#define OVSDB_MUTATORS                              \
+    OVSDB_MUTATOR(OVSDB_M_ADD, "+=")                \
+    OVSDB_MUTATOR(OVSDB_M_SUB, "-=")                \
+    OVSDB_MUTATOR(OVSDB_M_MUL, "*=")                \
+    OVSDB_MUTATOR(OVSDB_M_DIV, "/=")                \
+    OVSDB_MUTATOR(OVSDB_M_MOD, "%=")                \
+    OVSDB_MUTATOR(OVSDB_M_INSERT, "insert")         \
+    OVSDB_MUTATOR(OVSDB_M_DELETE, "delete")
+
+enum ovsdb_mutator {
+#define OVSDB_MUTATOR(ENUM, NAME) ENUM,
+    OVSDB_MUTATORS
+#undef OVSDB_MUTATOR
+};
+
+struct ovsdb_error *ovsdb_mutator_from_string(const char *,
+                                              enum ovsdb_mutator *)
+    WARN_UNUSED_RESULT;
+const char *ovsdb_mutator_to_string(enum ovsdb_mutator);
+
+struct ovsdb_mutation {
+    enum ovsdb_mutator mutator;
+    const struct ovsdb_column *column;
+    struct ovsdb_datum arg;
+    struct ovsdb_type type;
+};
+
+struct ovsdb_mutation_set {
+    struct ovsdb_mutation *mutations;
+    size_t n_mutations;
+};
+
+#define OVSDB_MUTATION_SET_INITIALIZER { NULL, 0 }
+
+struct ovsdb_error *ovsdb_mutation_set_from_json(
+    const struct ovsdb_table_schema *,
+    const struct json *, const struct ovsdb_symbol_table *,
+    struct ovsdb_mutation_set *) WARN_UNUSED_RESULT;
+struct json *ovsdb_mutation_set_to_json(const struct ovsdb_mutation_set *);
+void ovsdb_mutation_set_destroy(struct ovsdb_mutation_set *);
+struct ovsdb_error *ovsdb_mutation_set_execute(
+    struct ovsdb_row *, const struct ovsdb_mutation_set *);
+
+#endif /* ovsdb/mutation.h */
index ca10779f75b5fb52e319cb39891e14c32bef3bd5..60c0393a3b710f0552de61f127febc876c976892 100644 (file)
@@ -24,6 +24,7 @@ TESTSUITE_AT = \
        tests/ovsdb-table.at \
        tests/ovsdb-row.at \
        tests/ovsdb-condition.at \
+       tests/ovsdb-mutation.at \
        tests/ovsdb-query.at \
        tests/ovsdb-transaction.at \
        tests/ovsdb-execution.at \
index 8b2bc0b1fb43d9b4583e8899f87d67555c9cddfb..3a360416afd0acc460613d002ee282b34a62bda3 100644 (file)
@@ -104,6 +104,30 @@ OVSDB_CHECK_EXECUTION([insert rows, update rows by value],
 [{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"nought","number":0},{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
 ]])
 
+OVSDB_CHECK_EXECUTION([insert rows, mutate rows],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"}]]],
+   [[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "first"}]]],
+   [[[{"op": "mutate",
+       "table": "ordinals",
+       "where": [["name", "==", "zero"]],
+       "mutations": [["number", "+=", 2]]}]]],
+   [[[{"op": "select",
+       "table": "ordinals",
+       "where": [],
+       "sort": ["number"]}]]]],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"count":1}]
+[{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<2>"],"name":"one","number":1},{"_uuid":["uuid","<0>"],"_version":["uuid","<3>"],"name":"zero","number":2}]}]
+]])
+
 OVSDB_CHECK_EXECUTION([insert rows, delete by named-uuid],
   [ORDINAL_SCHEMA],
   [[[[{"op": "insert",
diff --git a/tests/ovsdb-mutation.at b/tests/ovsdb-mutation.at
new file mode 100644 (file)
index 0000000..c1d2fed
--- /dev/null
@@ -0,0 +1,700 @@
+AT_BANNER([OVSDB -- mutations])
+
+OVSDB_CHECK_POSITIVE([null mutation],
+  [[parse-mutations \
+    '{"columns": {"name": {"type": "string"}}}' \
+    '[]']],
+  [[[]]])
+
+OVSDB_CHECK_POSITIVE([mutations on scalars],
+  [[parse-mutations \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[["i", "+=", 0]]' \
+    '[["i", "-=", 1]]' \
+    '[["i", "*=", 2]]' \
+    '[["i", "/=", 3]]' \
+    '[["i", "%=", 4]]' \
+    '[["r", "+=", 0.5]]' \
+    '[["r", "-=", 1.5]]' \
+    '[["r", "*=", 2.5]]' \
+    '[["r", "/=", 3.5]]']],
+  [[[["i","+=",0]]
+[["i","-=",1]]
+[["i","*=",2]]
+[["i","/=",3]]
+[["i","%=",4]]
+[["r","+=",0.5]]
+[["r","-=",1.5]]
+[["r","*=",2.5]]
+[["r","/=",3.5]]]],
+  [mutation])
+
+AT_SETUP([disallowed mutations on scalars])
+AT_KEYWORDS([ovsdb negative mutation])
+OVS_CHECK_LCOV([[test-ovsdb parse-mutations \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[["i", "xxx", 1]]' \
+    '[["i", "insert", 1]]' \
+    '[["i", "delete", 2]]' \
+    '[["r", "%=", 0.5]]' \
+    '[["r", "insert", 1.5]]' \
+    '[["r", "delete", 2.5]]' \
+    '[["b", "+=", true]]' \
+    '[["b", "-=", false]]' \
+    '[["b", "*=", true]]' \
+    '[["b", "/=", false]]' \
+    '[["b", "%=", true]]' \
+    '[["b", "insert", false]]' \
+    '[["b", "delete", true]]' \
+    '[["s", "+=", "a"]]' \
+    '[["s", "-=", "b"]]' \
+    '[["s", "*=", "c"]]' \
+    '[["s", "/=", "d"]]' \
+    '[["s", "%=", "e"]]' \
+    '[["s", "insert", "f"]]' \
+    '[["s", "delete", "g"]]' \
+    '[["u", "+=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "-=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "*=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "/=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "insert", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "delete", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]']],
+  [1], [],
+  [[test-ovsdb: unknown mutator: No mutator named xxx.
+test-ovsdb: syntax "["i","insert",1]": syntax error: Type mismatch: "insert" operator may not be applied to column i of type integer.
+test-ovsdb: syntax "["i","delete",2]": syntax error: Type mismatch: "delete" operator may not be applied to column i of type integer.
+test-ovsdb: syntax "["r","%=",0.5]": syntax error: Type mismatch: "%=" operator may not be applied to column r of type real.
+test-ovsdb: syntax "["r","insert",1.5]": syntax error: Type mismatch: "insert" operator may not be applied to column r of type real.
+test-ovsdb: syntax "["r","delete",2.5]": syntax error: Type mismatch: "delete" operator may not be applied to column r of type real.
+test-ovsdb: syntax "["b","+=",true]": syntax error: Type mismatch: "+=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","-=",false]": syntax error: Type mismatch: "-=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","*=",true]": syntax error: Type mismatch: "*=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","/=",false]": syntax error: Type mismatch: "/=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","%=",true]": syntax error: Type mismatch: "%=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","insert",false]": syntax error: Type mismatch: "insert" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","delete",true]": syntax error: Type mismatch: "delete" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["s","+=","a"]": syntax error: Type mismatch: "+=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","-=","b"]": syntax error: Type mismatch: "-=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","*=","c"]": syntax error: Type mismatch: "*=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","/=","d"]": syntax error: Type mismatch: "/=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","%=","e"]": syntax error: Type mismatch: "%=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","insert","f"]": syntax error: Type mismatch: "insert" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","delete","g"]": syntax error: Type mismatch: "delete" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["u","+=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: "+=" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","-=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: "-=" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","*=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: "*=" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","/=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: "/=" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","insert",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: "insert" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","delete",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: "delete" operator may not be applied to column u of type uuid.
+]])
+AT_CLEANUP
+
+OVSDB_CHECK_POSITIVE([mutations on sets],
+  [[parse-mutations \
+    '{"columns":
+        {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}},
+         "r": {"type": {"key": "real", "min": 0, "max": "unlimited"}},
+         "b": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}},
+        "s": {"type": {"key": "string", "min": 0, "max": "unlimited"}},
+         "u": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \
+    '[["i", "+=", 1]]' \
+    '[["i", "-=", 2]]' \
+    '[["i", "*=", 3]]' \
+    '[["i", "/=", 4]]' \
+    '[["i", "%=", 5]]' \
+    '[["i", "insert", ["set", [1, 2]]]]' \
+    '[["i", "delete", ["set", [1, 2, 3]]]]' \
+    '[["r", "+=", 1]]' \
+    '[["r", "-=", 2]]' \
+    '[["r", "*=", 3]]' \
+    '[["r", "/=", 4]]' \
+    '[["r", "insert", ["set", [1, 2]]]]' \
+    '[["r", "delete", ["set", [1, 2, 3]]]]' \
+    '[["b", "insert", ["set", [true]]]]' \
+    '[["b", "delete", ["set", [false]]]]' \
+    '[["s", "insert", ["set", ["a"]]]]' \
+    '[["s", "delete", ["set", ["a", "b"]]]]' \
+    '[["u", "insert", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]' \
+    '[["u", "delete", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"],
+                ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]]]' \
+]],
+  [[[["i","+=",1]]
+[["i","-=",2]]
+[["i","*=",3]]
+[["i","/=",4]]
+[["i","%=",5]]
+[["i","insert",["set",[1,2]]]]
+[["i","delete",["set",[1,2,3]]]]
+[["r","+=",1]]
+[["r","-=",2]]
+[["r","*=",3]]
+[["r","/=",4]]
+[["r","insert",["set",[1,2]]]]
+[["r","delete",["set",[1,2,3]]]]
+[["b","insert",["set",[true]]]]
+[["b","delete",["set",[false]]]]
+[["s","insert",["set",["a"]]]]
+[["s","delete",["set",["a","b"]]]]
+[["u","insert",["set",[["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]
+[["u","delete",["set",[["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]]],
+  [mutation])
+
+OVSDB_CHECK_POSITIVE([executing null mutation],
+  [[execute-mutations \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[[]]' \
+    '[{"i": 0},
+      {"i": 1},
+      {"i": 2}']]],
+  [mutation  0:
+row 0: no change
+row 1: no change
+row 2: no change
+])
+
+OVSDB_CHECK_POSITIVE([executing mutations on integers],
+  [[execute-mutations \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[[["i", "+=", 1]],
+      [["i", "-=", 2]],
+      [["i", "*=", 3]],
+      [["i", "/=", 4]],
+      [["i", "%=", 2]]]' \
+    '[{"i": 0},
+      {"i": 1},
+      {"i": 2}']]],
+  [mutation  0:
+row 0: {"i":1}
+row 1: {"i":2}
+row 2: {"i":3}
+
+mutation  1:
+row 0: {"i":-2}
+row 1: {"i":-1}
+row 2: {"i":0}
+
+mutation  2:
+row 0: no change
+row 1: {"i":3}
+row 2: {"i":6}
+
+mutation  3:
+row 0: no change
+row 1: {"i":0}
+row 2: {"i":0}
+
+mutation  4:
+row 0: no change
+row 1: no change
+row 2: {"i":0}
+], [mutation])
+
+OVSDB_CHECK_POSITIVE([integer overflow detection],
+  [[execute-mutations \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[[["i", "+=", 9223372036854775807]],
+      [["i", "+=", -9223372036854775808]],
+      [["i", "-=", -9223372036854775808]],
+      [["i", "-=", 9223372036854775807]],
+      [["i", "*=", 3037000500]],
+      [["i", "/=", -1]],
+      [["i", "/=", 0]]]' \
+    '[{"i": 0},
+      {"i": 1},
+      {"i": -1},
+      {"i": 9223372036854775807},
+      {"i": -9223372036854775808},
+      {"i": 3037000500},
+      {"i": -3037000500}']]],
+  [mutation  0:
+row 0: {"i":9223372036854775807}
+row 1: range error: Result of "+=" operation is out of range.
+row 2: {"i":9223372036854775806}
+row 3: range error: Result of "+=" operation is out of range.
+row 4: {"i":-1}
+row 5: range error: Result of "+=" operation is out of range.
+row 6: {"i":9223372033817775307}
+
+mutation  1:
+row 0: {"i":-9223372036854775808}
+row 1: {"i":-9223372036854775807}
+row 2: range error: Result of "+=" operation is out of range.
+row 3: {"i":-1}
+row 4: range error: Result of "+=" operation is out of range.
+row 5: {"i":-9223372033817775308}
+row 6: range error: Result of "+=" operation is out of range.
+
+mutation  2:
+row 0: range error: Result of "-=" operation is out of range.
+row 1: range error: Result of "-=" operation is out of range.
+row 2: {"i":9223372036854775807}
+row 3: range error: Result of "-=" operation is out of range.
+row 4: {"i":0}
+row 5: range error: Result of "-=" operation is out of range.
+row 6: {"i":9223372033817775308}
+
+mutation  3:
+row 0: {"i":-9223372036854775807}
+row 1: {"i":-9223372036854775806}
+row 2: {"i":-9223372036854775808}
+row 3: {"i":0}
+row 4: range error: Result of "-=" operation is out of range.
+row 5: {"i":-9223372033817775307}
+row 6: range error: Result of "-=" operation is out of range.
+
+mutation  4:
+row 0: no change
+row 1: {"i":3037000500}
+row 2: {"i":-3037000500}
+row 3: range error: Result of "*=" operation is out of range.
+row 4: range error: Result of "*=" operation is out of range.
+row 5: range error: Result of "*=" operation is out of range.
+row 6: range error: Result of "*=" operation is out of range.
+
+mutation  5:
+row 0: no change
+row 1: {"i":-1}
+row 2: {"i":1}
+row 3: {"i":-9223372036854775807}
+row 4: range error: Result of "/=" operation is out of range.
+row 5: {"i":-3037000500}
+row 6: {"i":3037000500}
+
+mutation  6:
+row 0: domain error: Division by zero.
+row 1: domain error: Division by zero.
+row 2: domain error: Division by zero.
+row 3: domain error: Division by zero.
+row 4: domain error: Division by zero.
+row 5: domain error: Division by zero.
+row 6: domain error: Division by zero.
+], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on reals],
+  [[execute-mutations \
+    '{"columns": {"r": {"type": "real"}}}' \
+    '[[["r", "+=", 0.5]],
+      [["r", "-=", 1.5]],
+      [["r", "*=", 2.5]],
+      [["r", "/=", 4]]]' \
+    '[{"r": 0},
+      {"r": -2.5},
+      {"r": 1.25}']]],
+  [mutation  0:
+row 0: {"r":0.5}
+row 1: {"r":-2}
+row 2: {"r":1.75}
+
+mutation  1:
+row 0: {"r":-1.5}
+row 1: {"r":-4}
+row 2: {"r":-0.25}
+
+mutation  2:
+row 0: no change
+row 1: {"r":-6.25}
+row 2: {"r":3.125}
+
+mutation  3:
+row 0: no change
+row 1: {"r":-0.625}
+row 2: {"r":0.3125}
+], [mutation])
+
+OVSDB_CHECK_POSITIVE([real overflow detection],
+  [[execute-mutations \
+    '{"columns": {"r": {"type": "real"}}}' \
+    '[[["r", "+=", 1.7976931348623157e+308]],
+      [["r", "-=", 1.7976931348623157e+308]],
+      [["r", "*=", 2]],
+      [["r", "/=", 4]],
+      [["r", "/=", 0.5]],
+      [["r", "/=", 0]]]' \
+    '[{"r": 0},
+      {"r": 1.7976931348623157e+308},
+      {"r": -1.7976931348623157e+308}']]],
+  [mutation  0:
+row 0: {"r":1.79769313486232e+308}
+row 1: range error: Result of "+=" operation is out of range.
+row 2: {"r":0}
+
+mutation  1:
+row 0: {"r":-1.79769313486232e+308}
+row 1: {"r":0}
+row 2: range error: Result of "-=" operation is out of range.
+
+mutation  2:
+row 0: no change
+row 1: range error: Result of "*=" operation is out of range.
+row 2: range error: Result of "*=" operation is out of range.
+
+mutation  3:
+row 0: no change
+row 1: {"r":4.49423283715579e+307}
+row 2: {"r":-4.49423283715579e+307}
+
+mutation  4:
+row 0: no change
+row 1: range error: Result of "/=" operation is out of range.
+row 2: range error: Result of "/=" operation is out of range.
+
+mutation  5:
+row 0: domain error: Division by zero.
+row 1: domain error: Division by zero.
+row 2: domain error: Division by zero.
+], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on integer sets],
+  [[execute-mutations \
+    '{"columns": {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}}}}' \
+    '[[["i", "+=", 1]],
+      [["i", "-=", 2]],
+      [["i", "*=", 3]],
+      [["i", "/=", 4]],
+      [["i", "%=", 2]],
+      [["i", "insert", ["set", [1]]]],
+      [["i", "insert", ["set", [2, 3]]]],
+      [["i", "delete", ["set", [1]]]],
+      [["i", "delete", ["set", [2, 3]]]]]' \
+    '[{"i": ["set", []]},
+      {"i": ["set", [0]]},
+      {"i": ["set", [0, 1]]},
+      {"i": ["set", [0, 1, 2]]}']]],
+  [[mutation  0:
+row 0: no change
+row 1: {"i":["set",[1]]}
+row 2: {"i":["set",[1,2]]}
+row 3: {"i":["set",[1,2,3]]}
+
+mutation  1:
+row 0: no change
+row 1: {"i":["set",[-2]]}
+row 2: {"i":["set",[-2,-1]]}
+row 3: {"i":["set",[-2,-1,0]]}
+
+mutation  2:
+row 0: no change
+row 1: no change
+row 2: {"i":["set",[0,3]]}
+row 3: {"i":["set",[0,3,6]]}
+
+mutation  3:
+row 0: no change
+row 1: no change
+row 2: constraint violation: Result of "/=" operation contains duplicates.
+row 3: constraint violation: Result of "/=" operation contains duplicates.
+
+mutation  4:
+row 0: no change
+row 1: no change
+row 2: no change
+row 3: constraint violation: Result of "%=" operation contains duplicates.
+
+mutation  5:
+row 0: {"i":["set",[1]]}
+row 1: {"i":["set",[0,1]]}
+row 2: no change
+row 3: no change
+
+mutation  6:
+row 0: {"i":["set",[2,3]]}
+row 1: {"i":["set",[0,2,3]]}
+row 2: {"i":["set",[0,1,2,3]]}
+row 3: {"i":["set",[0,1,2,3]]}
+
+mutation  7:
+row 0: no change
+row 1: no change
+row 2: {"i":["set",[0]]}
+row 3: {"i":["set",[0,2]]}
+
+mutation  8:
+row 0: no change
+row 1: no change
+row 2: no change
+row 3: {"i":["set",[0,1]]}
+]], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on real sets],
+  [[execute-mutations \
+    '{"columns": {"r": {"type": {"key": "real", "min": 0, "max": "unlimited"}}}}' \
+    '[[["r", "+=", 0.5]],
+      [["r", "-=", 1.5]],
+      [["r", "*=", 2.5]],
+      [["r", "/=", 4]],
+      [["r", "*=", 0]],
+      [["r", "insert", ["set", [1.5]]]],
+      [["r", "insert", ["set", [3]]]],
+      [["r", "delete", ["set", [1.5, 3.5]]]],
+      [["r", "delete", ["set", [0.5, 1.5, 2.5]]]]]' \
+    '[{"r": ["set", []]},
+      {"r": ["set", [0.5]]},
+      {"r": ["set", [0.5, 1.5]]},
+      {"r": ["set", [0.5, 1.5, 2.5]]}']]],
+  [[mutation  0:
+row 0: no change
+row 1: {"r":["set",[1]]}
+row 2: {"r":["set",[1,2]]}
+row 3: {"r":["set",[1,2,3]]}
+
+mutation  1:
+row 0: no change
+row 1: {"r":["set",[-1]]}
+row 2: {"r":["set",[-1,0]]}
+row 3: {"r":["set",[-1,0,1]]}
+
+mutation  2:
+row 0: no change
+row 1: {"r":["set",[1.25]]}
+row 2: {"r":["set",[1.25,3.75]]}
+row 3: {"r":["set",[1.25,3.75,6.25]]}
+
+mutation  3:
+row 0: no change
+row 1: {"r":["set",[0.125]]}
+row 2: {"r":["set",[0.125,0.375]]}
+row 3: {"r":["set",[0.125,0.375,0.625]]}
+
+mutation  4:
+row 0: no change
+row 1: {"r":["set",[0]]}
+row 2: constraint violation: Result of "*=" operation contains duplicates.
+row 3: constraint violation: Result of "*=" operation contains duplicates.
+
+mutation  5:
+row 0: {"r":["set",[1.5]]}
+row 1: {"r":["set",[0.5,1.5]]}
+row 2: no change
+row 3: no change
+
+mutation  6:
+row 0: {"r":["set",[3]]}
+row 1: {"r":["set",[0.5,3]]}
+row 2: {"r":["set",[0.5,1.5,3]]}
+row 3: {"r":["set",[0.5,1.5,2.5,3]]}
+
+mutation  7:
+row 0: no change
+row 1: no change
+row 2: {"r":["set",[0.5]]}
+row 3: {"r":["set",[0.5,2.5]]}
+
+mutation  8:
+row 0: no change
+row 1: {"r":["set",[]]}
+row 2: {"r":["set",[]]}
+row 3: {"r":["set",[]]}
+]], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on boolean sets],
+  [[execute-mutations \
+    '{"columns": {"b": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}}}}' \
+    '[[["b", "insert", ["set", [false]]]],
+      [["b", "insert", ["set", [true]]]],
+      [["b", "insert", ["set", [false, true]]]],
+      [["b", "delete", ["set", [false]]]],
+      [["b", "delete", ["set", [true]]]],
+      [["b", "delete", ["set", [true, false]]]]]' \
+    '[{"b": ["set", []]},
+      {"b": ["set", [false]]},
+      {"b": ["set", [true]]},
+      {"b": ["set", [false, true]]}']]],
+  [[mutation  0:
+row 0: {"b":["set",[false]]}
+row 1: no change
+row 2: {"b":["set",[false,true]]}
+row 3: no change
+
+mutation  1:
+row 0: {"b":["set",[true]]}
+row 1: {"b":["set",[false,true]]}
+row 2: no change
+row 3: no change
+
+mutation  2:
+row 0: {"b":["set",[false,true]]}
+row 1: {"b":["set",[false,true]]}
+row 2: {"b":["set",[false,true]]}
+row 3: no change
+
+mutation  3:
+row 0: no change
+row 1: {"b":["set",[]]}
+row 2: no change
+row 3: {"b":["set",[true]]}
+
+mutation  4:
+row 0: no change
+row 1: no change
+row 2: {"b":["set",[]]}
+row 3: {"b":["set",[false]]}
+
+mutation  5:
+row 0: no change
+row 1: {"b":["set",[]]}
+row 2: {"b":["set",[]]}
+row 3: {"b":["set",[]]}
+]], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on string sets],
+  [[execute-mutations \
+    '{"columns": {"s": {"type": {"key": "string", "min": 0, "max": "unlimited"}}}}' \
+    '[[["s", "insert", ["set", ["a"]]]],
+      [["s", "insert", ["set", ["b"]]]],
+      [["s", "insert", ["set", ["c", "d"]]]],
+      [["s", "delete", ["set", ["a"]]]],
+      [["s", "delete", ["set", ["b"]]]],
+      [["s", "delete", ["set", ["c", "d"]]]]]' \
+    '[{"s": ["set", []]},
+      {"s": ["set", ["a"]]},
+      {"s": ["set", ["a", "b"]]},
+      {"s": ["set", ["a", "b", "c", "d"]]}']]],
+  [[mutation  0:
+row 0: {"s":["set",["a"]]}
+row 1: no change
+row 2: no change
+row 3: no change
+
+mutation  1:
+row 0: {"s":["set",["b"]]}
+row 1: {"s":["set",["a","b"]]}
+row 2: no change
+row 3: no change
+
+mutation  2:
+row 0: {"s":["set",["c","d"]]}
+row 1: {"s":["set",["a","c","d"]]}
+row 2: {"s":["set",["a","b","c","d"]]}
+row 3: no change
+
+mutation  3:
+row 0: no change
+row 1: {"s":["set",[]]}
+row 2: {"s":["set",["b"]]}
+row 3: {"s":["set",["b","c","d"]]}
+
+mutation  4:
+row 0: no change
+row 1: no change
+row 2: {"s":["set",["a"]]}
+row 3: {"s":["set",["a","c","d"]]}
+
+mutation  5:
+row 0: no change
+row 1: no change
+row 2: no change
+row 3: {"s":["set",["a","b"]]}
+]], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on uuid sets],
+  [[execute-mutations \
+    '{"columns": {"u": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \
+    '[[["u", "insert", ["set", [["uuid", "ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]]],
+      [["u", "insert", ["set", [["uuid", "a60fe7ff-317b-4568-9106-892b37445313"]]]]],
+      [["u", "insert", ["set", [["uuid", "2607d30e-e652-4927-9fec-8bbf1b60c7e9"]]]]],
+      [["u", "delete", ["set", [["uuid", "ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]]],
+      [["u", "delete", ["set", [["uuid", "a60fe7ff-317b-4568-9106-892b37445313"]]]]],
+      [["u", "delete", ["set", [["uuid", "2607d30e-e652-4927-9fec-8bbf1b60c7e9"]]]]]]' \
+    '[{"u": ["set", []]},
+      {"u": ["set", [["uuid", "ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]},
+      {"u": ["set", [["uuid", "a60fe7ff-317b-4568-9106-892b37445313"]]]},
+      {"u": ["set", [["uuid", "2607d30e-e652-4927-9fec-8bbf1b60c7e9"]]]}']]],
+  [[mutation  0:
+row 0: {"u":["set",[["uuid","ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]}
+row 1: no change
+row 2: {"u":["set",[["uuid","a60fe7ff-317b-4568-9106-892b37445313"],["uuid","ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]}
+row 3: {"u":["set",[["uuid","2607d30e-e652-4927-9fec-8bbf1b60c7e9"],["uuid","ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]}
+
+mutation  1:
+row 0: {"u":["set",[["uuid","a60fe7ff-317b-4568-9106-892b37445313"]]]}
+row 1: {"u":["set",[["uuid","a60fe7ff-317b-4568-9106-892b37445313"],["uuid","ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]}
+row 2: no change
+row 3: {"u":["set",[["uuid","2607d30e-e652-4927-9fec-8bbf1b60c7e9"],["uuid","a60fe7ff-317b-4568-9106-892b37445313"]]]}
+
+mutation  2:
+row 0: {"u":["set",[["uuid","2607d30e-e652-4927-9fec-8bbf1b60c7e9"]]]}
+row 1: {"u":["set",[["uuid","2607d30e-e652-4927-9fec-8bbf1b60c7e9"],["uuid","ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]}
+row 2: {"u":["set",[["uuid","2607d30e-e652-4927-9fec-8bbf1b60c7e9"],["uuid","a60fe7ff-317b-4568-9106-892b37445313"]]]}
+row 3: no change
+
+mutation  3:
+row 0: no change
+row 1: {"u":["set",[]]}
+row 2: no change
+row 3: no change
+
+mutation  4:
+row 0: no change
+row 1: no change
+row 2: {"u":["set",[]]}
+row 3: no change
+
+mutation  5:
+row 0: no change
+row 1: no change
+row 2: no change
+row 3: {"u":["set",[]]}
+]], [mutation])
+
+
+OVSDB_CHECK_POSITIVE([executing mutations on integer maps],
+  [[execute-mutations \
+    '{"columns": {"i": {"type": {"key": "integer", "value": "integer", "min": 0, "max": "unlimited"}}}}' \
+    '[[["i", "insert", ["map", [[1, 2]]]]],
+      [["i", "insert", ["map", [[2, 4], [3, 5]]]]],
+      [["i", "delete", ["map", [[1, 2]]]]],
+      [["i", "delete", ["map", [[2, 3]]]]],
+      [["i", "delete", ["set", [1]]]],
+      [["i", "delete", ["set", [2, 3]]]]]' \
+    '[{"i": ["map", []]},
+      {"i": ["map", [[1, 2]]]},
+      {"i": ["map", [[1, 3], [2, 3]]]},
+      {"i": ["map", [[3, 5]]]}']]],
+  [[mutation  0:
+row 0: {"i":["map",[[1,2]]]}
+row 1: no change
+row 2: no change
+row 3: {"i":["map",[[1,2],[3,5]]]}
+
+mutation  1:
+row 0: {"i":["map",[[2,4],[3,5]]]}
+row 1: {"i":["map",[[1,2],[2,4],[3,5]]]}
+row 2: {"i":["map",[[1,3],[2,3],[3,5]]]}
+row 3: {"i":["map",[[2,4],[3,5]]]}
+
+mutation  2:
+row 0: no change
+row 1: {"i":["map",[]]}
+row 2: no change
+row 3: no change
+
+mutation  3:
+row 0: no change
+row 1: no change
+row 2: {"i":["map",[[1,3]]]}
+row 3: no change
+
+mutation  4:
+row 0: no change
+row 1: {"i":["map",[]]}
+row 2: {"i":["map",[[2,3]]]}
+row 3: no change
+
+mutation  5:
+row 0: no change
+row 1: no change
+row 2: {"i":["map",[[1,3]]]}
+row 3: {"i":["map",[]]}
+]], [mutation])
index 6e3f63d852d4880eb57a9953d97e45494b4bcac2..6f84dca407bc0ce19e365f95d683c607d9a97635 100644 (file)
@@ -42,6 +42,7 @@ m4_include([tests/ovsdb-column.at])
 m4_include([tests/ovsdb-table.at])
 m4_include([tests/ovsdb-row.at])
 m4_include([tests/ovsdb-condition.at])
+m4_include([tests/ovsdb-mutation.at])
 m4_include([tests/ovsdb-query.at])
 m4_include([tests/ovsdb-transaction.at])
 m4_include([tests/ovsdb-execution.at])
index a4a5f019a76c642cd80b6468730935827a0c3a9b..a0476a922becb52570a2410b7ab415da7a9ddab4 100644 (file)
@@ -34,6 +34,7 @@
 #include "ovsdb/condition.h"
 #include "ovsdb/file.h"
 #include "ovsdb/log.h"
+#include "ovsdb/mutation.h"
 #include "ovsdb/ovsdb.h"
 #include "ovsdb/query.h"
 #include "ovsdb/row.h"
@@ -141,6 +142,10 @@ usage(void)
            "    parse each CONDITION on TABLE, and re-serialize\n"
            "  evaluate-conditions TABLE [CONDITION,...] [ROW,...]\n"
            "    test CONDITIONS on TABLE against each ROW, print results\n"
+           "  parse-mutations TABLE MUTATION...\n"
+           "    parse each MUTATION on TABLE, and re-serialize\n"
+           "  execute-mutations TABLE [MUTATION,...] [ROW,...]\n"
+           "    execute MUTATIONS on TABLE on each ROW, print results\n"
            "  query TABLE [ROW,...] [CONDITION,...]\n"
            "    add each ROW to TABLE, then query and print the rows that\n"
            "    satisfy each CONDITION.\n"
@@ -210,6 +215,15 @@ print_and_free_json(struct json *json)
     free(string);
 }
 
+static void
+print_and_free_ovsdb_error(struct ovsdb_error *error)
+{
+    char *string = ovsdb_error_to_string(error);
+    ovsdb_error_destroy(error);
+    puts(string);
+    free(string);
+}
+
 static void
 check_ovsdb_error(struct ovsdb_error *error)
 {
@@ -657,6 +671,135 @@ do_evaluate_conditions(int argc UNUSED, char *argv[])
     ovsdb_table_destroy(table); /* Also destroys 'ts'. */
 }
 
+static void
+do_parse_mutations(int argc, char *argv[])
+{
+    struct ovsdb_table_schema *ts;
+    struct json *json;
+    int exit_code = 0;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    for (i = 2; i < argc; i++) {
+        struct ovsdb_mutation_set set;
+        struct ovsdb_error *error;
+
+        json = parse_json(argv[i]);
+        error = ovsdb_mutation_set_from_json(ts, json, NULL, &set);
+        if (!error) {
+            print_and_free_json(ovsdb_mutation_set_to_json(&set));
+        } else {
+            char *s = ovsdb_error_to_string(error);
+            ovs_error(0, "%s", s);
+            free(s);
+            ovsdb_error_destroy(error);
+            exit_code = 1;
+        }
+        json_destroy(json);
+
+        ovsdb_mutation_set_destroy(&set);
+    }
+    ovsdb_table_schema_destroy(ts);
+
+    exit(exit_code);
+}
+
+static void
+do_execute_mutations(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct ovsdb_mutation_set *sets;
+    size_t n_sets;
+    struct ovsdb_row **rows;
+    size_t n_rows;
+    struct json *json;
+    size_t i, j;
+
+    /* Parse table schema, create table. */
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+
+    /* Parse mutations. */
+    json = parse_json(argv[2]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "MUTATION argument is not JSON array");
+    }
+    n_sets = json->u.array.n;
+    sets = xmalloc(n_sets * sizeof *sets);
+    for (i = 0; i < n_sets; i++) {
+        check_ovsdb_error(ovsdb_mutation_set_from_json(ts,
+                                                       json->u.array.elems[i],
+                                                       NULL, &sets[i]));
+    }
+    json_destroy(json);
+
+    /* Parse rows. */
+    json = parse_json(argv[3]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "ROW argument is not JSON array");
+    }
+    n_rows = json->u.array.n;
+    rows = xmalloc(n_rows * sizeof *rows);
+    for (i = 0; i < n_rows; i++) {
+        rows[i] = ovsdb_row_create(table);
+        check_ovsdb_error(ovsdb_row_from_json(rows[i], json->u.array.elems[i],
+                                              NULL, NULL));
+    }
+    json_destroy(json);
+
+    for (i = 0; i < n_sets; i++) {
+        printf("mutation %2d:\n", i);
+        for (j = 0; j < n_rows; j++) {
+            struct ovsdb_error *error;
+            struct ovsdb_row *row;
+
+            row = ovsdb_row_clone(rows[j]);
+            error = ovsdb_mutation_set_execute(row, &sets[i]);
+
+            printf("row %zu: ", j);
+            if (error) {
+                print_and_free_ovsdb_error(error);
+            } else {
+                struct ovsdb_column_set columns;
+                struct shash_node *node;
+
+                ovsdb_column_set_init(&columns);
+                SHASH_FOR_EACH (node, &ts->columns) {
+                    struct ovsdb_column *c = node->data;
+                    if (!ovsdb_datum_equals(&row->fields[c->index],
+                                            &rows[j]->fields[c->index],
+                                            &c->type)) {
+                        ovsdb_column_set_add(&columns, c);
+                    }
+                }
+                if (columns.n_columns) {
+                    print_and_free_json(ovsdb_row_to_json(row, &columns));
+                } else {
+                    printf("no change\n");
+                }
+                ovsdb_column_set_destroy(&columns);
+            }
+            ovsdb_row_destroy(row);
+        }
+        printf("\n");
+    }
+
+    for (i = 0; i < n_sets; i++) {
+        ovsdb_mutation_set_destroy(&sets[i]);
+    }
+    for (i = 0; i < n_rows; i++) {
+        ovsdb_row_destroy(rows[i]);
+    }
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
 struct do_query_cbdata {
     struct uuid *row_uuids;
     int *counts;
@@ -1564,6 +1707,8 @@ static struct command all_commands[] = {
     { "compare-rows", 2, INT_MAX, do_compare_rows },
     { "parse-conditions", 2, INT_MAX, do_parse_conditions },
     { "evaluate-conditions", 3, 3, do_evaluate_conditions },
+    { "parse-mutations", 2, INT_MAX, do_parse_mutations },
+    { "execute-mutations", 3, 3, do_execute_mutations },
     { "query", 3, 3, do_query },
     { "query-distinct", 4, 4, do_query_distinct },
     { "transact", 1, INT_MAX, do_transact },