From e9f8f9367e9e654b9582279608af86ea3744a2a0 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Wed, 16 Dec 2009 10:49:31 -0800 Subject: [PATCH] ovsdb: Add new "mutation" operation to transactions. --- lib/ovsdb-data.c | 114 ++++++- lib/ovsdb-data.h | 10 + ovsdb/SPECS | 124 +++++++ ovsdb/automake.mk | 2 + ovsdb/condition.c | 1 - ovsdb/execution.c | 61 ++++ ovsdb/mutation.c | 462 ++++++++++++++++++++++++++ ovsdb/mutation.h | 72 ++++ tests/automake.mk | 1 + tests/ovsdb-execution.at | 24 ++ tests/ovsdb-mutation.at | 700 +++++++++++++++++++++++++++++++++++++++ tests/ovsdb.at | 1 + tests/test-ovsdb.c | 145 ++++++++ 13 files changed, 1704 insertions(+), 13 deletions(-) create mode 100644 ovsdb/mutation.c create mode 100644 ovsdb/mutation.h create mode 100644 tests/ovsdb-mutation.at diff --git a/lib/ovsdb-data.c b/lib/ovsdb-data.c index df445d775..0d7749b17 100644 --- a/lib/ovsdb-data.c +++ b/lib/ovsdb-data.c @@ -18,6 +18,7 @@ #include "ovsdb-data.h" #include +#include #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); + } +} struct ovsdb_symbol_table { struct shash sh; diff --git a/lib/ovsdb-data.h b/lib/ovsdb-data.h index b31aa5d49..3f2d48943 100644 --- a/lib/ovsdb-data.h +++ b/lib/ovsdb-data.h @@ -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) diff --git a/ovsdb/SPECS b/ovsdb/SPECS index 93db15d4c..9fd654be6 100644 --- a/ovsdb/SPECS +++ b/ovsdb/SPECS @@ -586,6 +586,79 @@ Notation for the Wire Protocol One of "<", "<=", "==", "!=", ">=", ">", "includes", "excludes". + + + A 3-element JSON array of the form [, , ] + that represents a change to a column value. + + Except as otherwise specified below, must have the same + type as . + + The meaning depends on the type of : + + integer + real + + must be "+=", "-=", "*=", "/=" or (integer only) + "%=". The value of is changed to the sum, + difference, product, quotient, or remainder, respectively, + of and . + + boolean + string + uuid + + No valid s are currently defined for these types. + + set + + Any 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. must be a + scalar value of the same type as the set's element type. + + If is "insert", then each of the values in the + set in is added to if it is not already + present. The required type of is slightly + relaxed, in that it may have fewer than the minimum number + of elements specified by the column's type. + + If is "delete", then each of the values in the + set in is removed from if it is present + there. The required type is slightly relaxed in that + may have more or less than the maximum number of + elements specified by the column's type. + + map + + must be "insert" or "delete". + + If is "insert", then each of the key-value pairs + in the map in is added to if its key is + not already present. The required type of is + slightly relaxed, in that it may have fewer than the + minimum number of elements specified by the column's type. + + If is "delete", then may have the same + type as (a map type) or it may be a set whose + element type is the same as 's key type: + + - If is a map, the mutation deletes each + key-value pair in whose key and value equal + one of the key-value pairs in . + + - If is a set, the mutation deletes each + key-value pair in whose key equals one of + the values in . + + For "delete", may have any number of elements, + regardless of restrictions on the number of elements in + . + + + + 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": required + "where": [*] required + "mutations": [*] required + +Result object members: + + "count": + +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 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 ...... diff --git a/ovsdb/automake.mk b/ovsdb/automake.mk index f3f5f9b9b..2732c5390 100644 --- a/ovsdb/automake.mk +++ b/ovsdb/automake.mk @@ -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 \ diff --git a/ovsdb/condition.c b/ovsdb/condition.c index 0342b8e89..f3f43003a 100644 --- a/ovsdb/condition.c +++ b/ovsdb/condition.c @@ -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, diff --git a/ovsdb/execution.c b/ovsdb/execution.c index 932bee238..67f6b8d06 100644 --- a/ovsdb/execution.c +++ b/ovsdb/execution.c @@ -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 index 000000000..ba56827c4 --- /dev/null +++ b/ovsdb/mutation.c @@ -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 + +#include "mutation.h" + +#include +#include + +#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 index 000000000..d466e281c --- /dev/null +++ b/ovsdb/mutation.h @@ -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 +#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 */ diff --git a/tests/automake.mk b/tests/automake.mk index ca10779f7..60c0393a3 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -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 \ diff --git a/tests/ovsdb-execution.at b/tests/ovsdb-execution.at index 8b2bc0b1f..3a360416a 100644 --- a/tests/ovsdb-execution.at +++ b/tests/ovsdb-execution.at @@ -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 index 000000000..c1d2fedf1 --- /dev/null +++ b/tests/ovsdb-mutation.at @@ -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]) diff --git a/tests/ovsdb.at b/tests/ovsdb.at index 6e3f63d85..6f84dca40 100644 --- a/tests/ovsdb.at +++ b/tests/ovsdb.at @@ -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]) diff --git a/tests/test-ovsdb.c b/tests/test-ovsdb.c index a4a5f019a..a0476a922 100644 --- a/tests/test-ovsdb.c +++ b/tests/test-ovsdb.c @@ -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 }, -- 2.43.0