aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am1
-rw-r--r--lib/automake.mk11
-rw-r--r--lib/compiler.h1
-rw-r--r--lib/ovsdb-data.c756
-rw-r--r--lib/ovsdb-data.h128
-rw-r--r--lib/ovsdb-error.c221
-rw-r--r--lib/ovsdb-error.h53
-rw-r--r--lib/ovsdb-parser.c167
-rw-r--r--lib/ovsdb-parser.h74
-rw-r--r--lib/ovsdb-types.c251
-rw-r--r--lib/ovsdb-types.h126
-rw-r--r--lib/sort.c70
-rw-r--r--lib/sort.h26
-rw-r--r--lib/vlog-modules.def5
-rw-r--r--ovsdb/SPECS628
-rw-r--r--ovsdb/automake.mk44
-rw-r--r--ovsdb/column.c232
-rw-r--r--ovsdb/column.h84
-rw-r--r--ovsdb/condition.c284
-rw-r--r--ovsdb/condition.h72
-rw-r--r--ovsdb/execution.c613
-rw-r--r--ovsdb/file.c360
-rw-r--r--ovsdb/file.h36
-rw-r--r--ovsdb/jsonrpc-server.c362
-rw-r--r--ovsdb/jsonrpc-server.h29
-rw-r--r--ovsdb/ovsdb-server.c223
-rw-r--r--ovsdb/ovsdb-tool.c204
-rw-r--r--ovsdb/ovsdb.c262
-rw-r--r--ovsdb/ovsdb.h73
-rw-r--r--ovsdb/query.c99
-rw-r--r--ovsdb/query.h37
-rw-r--r--ovsdb/row.c386
-rw-r--r--ovsdb/row.h139
-rw-r--r--ovsdb/table.c228
-rw-r--r--ovsdb/table.h63
-rw-r--r--ovsdb/transaction.c444
-rw-r--r--ovsdb/transaction.h41
-rw-r--r--ovsdb/trigger.c129
-rw-r--r--ovsdb/trigger.h44
-rw-r--r--tests/automake.mk17
-rw-r--r--tests/library.at1
-rw-r--r--tests/ovsdb-column.at18
-rw-r--r--tests/ovsdb-condition.at558
-rw-r--r--tests/ovsdb-data.at259
-rw-r--r--tests/ovsdb-execution.at321
-rw-r--r--tests/ovsdb-file.at282
-rw-r--r--tests/ovsdb-query.at535
-rw-r--r--tests/ovsdb-row.at277
-rw-r--r--tests/ovsdb-table.at31
-rw-r--r--tests/ovsdb-transaction.at384
-rw-r--r--tests/ovsdb-trigger.at173
-rw-r--r--tests/ovsdb-types.at90
-rw-r--r--tests/ovsdb.at35
-rw-r--r--tests/test-ovsdb.c1229
-rw-r--r--tests/testsuite.at2
-rwxr-xr-xtests/uuidfilt.pl21
56 files changed, 11239 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am
index 52f5e594..22ebb219 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -77,5 +77,6 @@ include include/automake.mk
include third-party/automake.mk
include debian/automake.mk
include vswitchd/automake.mk
+include ovsdb/automake.mk
include xenserver/automake.mk
include extras/ezio/automake.mk
diff --git a/lib/automake.mk b/lib/automake.mk
index 07f7ce71..918e4bb4 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -77,6 +77,15 @@ lib_libopenvswitch_a_SOURCES = \
lib/ofp-print.h \
lib/ofpbuf.c \
lib/ofpbuf.h \
+ lib/ovsdb-client.h \
+ lib/ovsdb-data.c \
+ lib/ovsdb-data.h \
+ lib/ovsdb-error.c \
+ lib/ovsdb-error.h \
+ lib/ovsdb-parser.c \
+ lib/ovsdb-parser.h \
+ lib/ovsdb-types.c \
+ lib/ovsdb-types.h \
lib/packets.c \
lib/packets.h \
lib/pcap.c \
@@ -104,6 +113,8 @@ lib_libopenvswitch_a_SOURCES = \
lib/signals.h \
lib/socket-util.c \
lib/socket-util.h \
+ lib/sort.c \
+ lib/sort.h \
lib/stp.c \
lib/stp.h \
lib/stream-fd.c \
diff --git a/lib/compiler.h b/lib/compiler.h
index 17e245fc..216dd6af 100644
--- a/lib/compiler.h
+++ b/lib/compiler.h
@@ -26,5 +26,6 @@
#define ALWAYS_INLINE __attribute__((always_inline))
#define likely(x) __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)
+#define WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))
#endif /* compiler.h */
diff --git a/lib/ovsdb-data.c b/lib/ovsdb-data.c
new file mode 100644
index 00000000..46298cb4
--- /dev/null
+++ b/lib/ovsdb-data.c
@@ -0,0 +1,756 @@
+/* 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 "ovsdb-data.h"
+
+#include <assert.h>
+
+#include "hash.h"
+#include "ovsdb-error.h"
+#include "json.h"
+#include "shash.h"
+#include "sort.h"
+
+static struct json *
+wrap_json(const char *name, struct json *wrapped)
+{
+ return json_array_create_2(json_string_create(name), wrapped);
+}
+
+void
+ovsdb_atom_init_default(union ovsdb_atom *atom, enum ovsdb_atomic_type type)
+{
+ switch (type) {
+ case OVSDB_TYPE_VOID:
+ NOT_REACHED();
+
+ case OVSDB_TYPE_INTEGER:
+ atom->integer = 0;
+ break;
+
+ case OVSDB_TYPE_REAL:
+ atom->real = 0.0;
+ break;
+
+ case OVSDB_TYPE_BOOLEAN:
+ atom->boolean = false;
+ break;
+
+ case OVSDB_TYPE_STRING:
+ atom->string = xmemdup("", 1);
+ break;
+
+ case OVSDB_TYPE_UUID:
+ uuid_zero(&atom->uuid);
+ break;
+
+ case OVSDB_N_TYPES:
+ default:
+ NOT_REACHED();
+ }
+}
+
+void
+ovsdb_atom_clone(union ovsdb_atom *new, const union ovsdb_atom *old,
+ enum ovsdb_atomic_type type)
+{
+ switch (type) {
+ case OVSDB_TYPE_VOID:
+ NOT_REACHED();
+
+ case OVSDB_TYPE_INTEGER:
+ new->integer = old->integer;
+ break;
+
+ case OVSDB_TYPE_REAL:
+ new->real = old->real;
+ break;
+
+ case OVSDB_TYPE_BOOLEAN:
+ new->boolean = old->boolean;
+ break;
+
+ case OVSDB_TYPE_STRING:
+ new->string = xstrdup(old->string);
+ break;
+
+ case OVSDB_TYPE_UUID:
+ new->uuid = old->uuid;
+ break;
+
+ case OVSDB_N_TYPES:
+ default:
+ NOT_REACHED();
+ }
+}
+
+void
+ovsdb_atom_swap(union ovsdb_atom *a, union ovsdb_atom *b)
+{
+ union ovsdb_atom tmp = *a;
+ *a = *b;
+ *b = tmp;
+}
+
+uint32_t
+ovsdb_atom_hash(const union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+ uint32_t basis)
+{
+ switch (type) {
+ case OVSDB_TYPE_VOID:
+ NOT_REACHED();
+
+ case OVSDB_TYPE_INTEGER:
+ return hash_int(atom->integer, basis);
+
+ case OVSDB_TYPE_REAL:
+ return hash_double(atom->real, basis);
+
+ case OVSDB_TYPE_BOOLEAN:
+ return hash_boolean(atom->boolean, basis);
+
+ case OVSDB_TYPE_STRING:
+ return hash_string(atom->string, basis);
+
+ case OVSDB_TYPE_UUID:
+ return hash_int(uuid_hash(&atom->uuid), basis);
+
+ case OVSDB_N_TYPES:
+ default:
+ NOT_REACHED();
+ }
+}
+
+int
+ovsdb_atom_compare_3way(const union ovsdb_atom *a,
+ const union ovsdb_atom *b,
+ enum ovsdb_atomic_type type)
+{
+ switch (type) {
+ case OVSDB_TYPE_VOID:
+ NOT_REACHED();
+
+ case OVSDB_TYPE_INTEGER:
+ return a->integer < b->integer ? -1 : a->integer > b->integer;
+
+ case OVSDB_TYPE_REAL:
+ return a->real < b->real ? -1 : a->real > b->real;
+
+ case OVSDB_TYPE_BOOLEAN:
+ return a->boolean - b->boolean;
+
+ case OVSDB_TYPE_STRING:
+ return strcmp(a->string, b->string);
+
+ case OVSDB_TYPE_UUID:
+ return uuid_compare_3way(&a->uuid, &b->uuid);
+
+ case OVSDB_N_TYPES:
+ default:
+ NOT_REACHED();
+ }
+}
+
+static struct ovsdb_error *
+unwrap_json(const struct json *json, const char *name,
+ enum json_type value_type, const struct json **value)
+{
+ if (json->type != JSON_ARRAY
+ || json->u.array.n != 2
+ || json->u.array.elems[0]->type != JSON_STRING
+ || (name && strcmp(json->u.array.elems[0]->u.string, name))
+ || json->u.array.elems[1]->type != value_type)
+ {
+ return ovsdb_syntax_error(json, NULL, "expected [\"%s\", <%s>]", name,
+ json_type_to_string(value_type));
+ }
+ *value = json->u.array.elems[1];
+ return NULL;
+}
+
+static struct ovsdb_error *
+parse_json_pair(const struct json *json,
+ const struct json **elem0, const struct json **elem1)
+{
+ if (json->type != JSON_ARRAY || json->u.array.n != 2) {
+ return ovsdb_syntax_error(json, NULL, "expected 2-element array");
+ }
+ *elem0 = json->u.array.elems[0];
+ *elem1 = json->u.array.elems[1];
+ return NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_atom_parse_uuid(struct uuid *uuid, const struct json *json,
+ const struct ovsdb_symbol_table *symtab)
+ WARN_UNUSED_RESULT;
+
+static struct ovsdb_error *
+ovsdb_atom_parse_uuid(struct uuid *uuid, const struct json *json,
+ const struct ovsdb_symbol_table *symtab)
+{
+ struct ovsdb_error *error0;
+ const struct json *value;
+
+ error0 = unwrap_json(json, "uuid", JSON_STRING, &value);
+ if (!error0) {
+ const char *uuid_string = json_string(value);
+ if (!uuid_from_string(uuid, uuid_string)) {
+ return ovsdb_syntax_error(json, NULL, "\"%s\" is not a valid UUID",
+ uuid_string);
+ }
+ } else if (symtab) {
+ struct ovsdb_error *error1;
+
+ error1 = unwrap_json(json, "named-uuid", JSON_STRING, &value);
+ if (!error1) {
+ const char *name = json_string(value);
+ const struct uuid *named_uuid;
+
+ ovsdb_error_destroy(error0);
+
+ named_uuid = ovsdb_symbol_table_get(symtab, name);
+ if (named_uuid) {
+ *uuid = *named_uuid;
+ return NULL;
+ } else {
+ return ovsdb_syntax_error(json, NULL,
+ "unknown named-uuid \"%s\"", name);
+ }
+ }
+ ovsdb_error_destroy(error1);
+ }
+
+ return error0;
+}
+
+struct ovsdb_error *
+ovsdb_atom_from_json(union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+ const struct json *json,
+ const struct ovsdb_symbol_table *symtab)
+{
+ switch (type) {
+ case OVSDB_TYPE_VOID:
+ NOT_REACHED();
+
+ case OVSDB_TYPE_INTEGER:
+ if (json->type == JSON_INTEGER) {
+ atom->integer = json->u.integer;
+ return NULL;
+ }
+ break;
+
+ case OVSDB_TYPE_REAL:
+ if (json->type == JSON_INTEGER) {
+ atom->real = json->u.integer;
+ return NULL;
+ } else if (json->type == JSON_REAL) {
+ atom->real = json->u.real;
+ return NULL;
+ }
+ break;
+
+ case OVSDB_TYPE_BOOLEAN:
+ if (json->type == JSON_TRUE) {
+ atom->boolean = true;
+ return NULL;
+ } else if (json->type == JSON_FALSE) {
+ atom->boolean = false;
+ return NULL;
+ }
+ break;
+
+ case OVSDB_TYPE_STRING:
+ if (json->type == JSON_STRING) {
+ atom->string = xstrdup(json->u.string);
+ return NULL;
+ }
+ break;
+
+ case OVSDB_TYPE_UUID:
+ return ovsdb_atom_parse_uuid(&atom->uuid, json, symtab);
+
+ case OVSDB_N_TYPES:
+ default:
+ NOT_REACHED();
+ }
+
+ return ovsdb_syntax_error(json, NULL, "expected %s",
+ ovsdb_atomic_type_to_string(type));
+}
+
+struct json *
+ovsdb_atom_to_json(const union ovsdb_atom *atom, enum ovsdb_atomic_type type)
+{
+ switch (type) {
+ case OVSDB_TYPE_VOID:
+ NOT_REACHED();
+
+ case OVSDB_TYPE_INTEGER:
+ return json_integer_create(atom->integer);
+
+ case OVSDB_TYPE_REAL:
+ return json_real_create(atom->real);
+
+ case OVSDB_TYPE_BOOLEAN:
+ return json_boolean_create(atom->boolean);
+
+ case OVSDB_TYPE_STRING:
+ return json_string_create(atom->string);
+
+ case OVSDB_TYPE_UUID:
+ return wrap_json("uuid", json_string_create_nocopy(
+ xasprintf(UUID_FMT, UUID_ARGS(&atom->uuid))));
+
+ case OVSDB_N_TYPES:
+ default:
+ NOT_REACHED();
+ }
+}
+
+static union ovsdb_atom *
+alloc_default_atoms(enum ovsdb_atomic_type type, size_t n)
+{
+ if (type != OVSDB_TYPE_VOID && n) {
+ union ovsdb_atom *atoms;
+ unsigned int i;
+
+ atoms = xmalloc(n * sizeof *atoms);
+ for (i = 0; i < n; i++) {
+ ovsdb_atom_init_default(&atoms[i], type);
+ }
+ return atoms;
+ } else {
+ /* Avoid wasting memory in the n == 0 case, because xmalloc(0) is
+ * treated as xmalloc(1). */
+ return NULL;
+ }
+}
+
+void
+ovsdb_datum_init_default(struct ovsdb_datum *datum,
+ const struct ovsdb_type *type)
+{
+ datum->n = type->n_min;
+ datum->keys = alloc_default_atoms(type->key_type, datum->n);
+ datum->values = alloc_default_atoms(type->value_type, datum->n);
+}
+
+static union ovsdb_atom *
+clone_atoms(const union ovsdb_atom *old, enum ovsdb_atomic_type type, size_t n)
+{
+ if (type != OVSDB_TYPE_VOID && n) {
+ union ovsdb_atom *new;
+ unsigned int i;
+
+ new = xmalloc(n * sizeof *new);
+ for (i = 0; i < n; i++) {
+ ovsdb_atom_clone(&new[i], &old[i], type);
+ }
+ return new;
+ } else {
+ /* Avoid wasting memory in the n == 0 case, because xmalloc(0) is
+ * treated as xmalloc(1). */
+ return NULL;
+ }
+}
+
+void
+ovsdb_datum_clone(struct ovsdb_datum *new, const struct ovsdb_datum *old,
+ const struct ovsdb_type *type)
+{
+ unsigned int n = old->n;
+ new->n = n;
+ new->keys = clone_atoms(old->keys, type->key_type, n);
+ new->values = clone_atoms(old->values, type->value_type, n);
+}
+
+static void
+free_data(enum ovsdb_atomic_type type,
+ union ovsdb_atom *atoms, size_t n_atoms)
+{
+ if (ovsdb_atom_needs_destruction(type)) {
+ unsigned int i;
+ for (i = 0; i < n_atoms; i++) {
+ ovsdb_atom_destroy(&atoms[i], type);
+ }
+ }
+ free(atoms);
+}
+
+void
+ovsdb_datum_destroy(struct ovsdb_datum *datum, const struct ovsdb_type *type)
+{
+ free_data(type->key_type, datum->keys, datum->n);
+ free_data(type->value_type, datum->values, datum->n);
+}
+
+void
+ovsdb_datum_swap(struct ovsdb_datum *a, struct ovsdb_datum *b)
+{
+ struct ovsdb_datum tmp = *a;
+ *a = *b;
+ *b = tmp;
+}
+
+struct ovsdb_datum_sort_cbdata {
+ const struct ovsdb_type *type;
+ struct ovsdb_datum *datum;
+};
+
+static int
+ovsdb_datum_sort_compare_cb(size_t a, size_t b, void *cbdata_)
+{
+ struct ovsdb_datum_sort_cbdata *cbdata = cbdata_;
+
+ return ovsdb_atom_compare_3way(&cbdata->datum->keys[a],
+ &cbdata->datum->keys[b],
+ cbdata->type->key_type);
+}
+
+static void
+ovsdb_datum_sort_swap_cb(size_t a, size_t b, void *cbdata_)
+{
+ struct ovsdb_datum_sort_cbdata *cbdata = cbdata_;
+
+ ovsdb_atom_swap(&cbdata->datum->keys[a], &cbdata->datum->keys[b]);
+ if (cbdata->type->value_type != OVSDB_TYPE_VOID) {
+ ovsdb_atom_swap(&cbdata->datum->values[a], &cbdata->datum->values[b]);
+ }
+}
+
+static struct ovsdb_error *
+ovsdb_datum_sort(struct ovsdb_datum *datum, const struct ovsdb_type *type)
+{
+ if (datum->n < 2) {
+ return NULL;
+ } else {
+ struct ovsdb_datum_sort_cbdata cbdata;
+ size_t i;
+
+ cbdata.type = type;
+ cbdata.datum = datum;
+ sort(datum->n, ovsdb_datum_sort_compare_cb, ovsdb_datum_sort_swap_cb,
+ &cbdata);
+
+ for (i = 0; i < datum->n - 1; i++) {
+ if (ovsdb_atom_equals(&datum->keys[i], &datum->keys[i + 1],
+ type->key_type)) {
+ if (ovsdb_type_is_map(type)) {
+ return ovsdb_error(NULL, "map contains duplicate key");
+ } else {
+ return ovsdb_error(NULL, "set contains duplicate");
+ }
+ }
+ }
+
+ return NULL;
+ }
+}
+
+struct ovsdb_error *
+ovsdb_datum_from_json(struct ovsdb_datum *datum,
+ const struct ovsdb_type *type,
+ const struct json *json,
+ const struct ovsdb_symbol_table *symtab)
+{
+ struct ovsdb_error *error;
+
+ if (ovsdb_type_is_scalar(type)) {
+ datum->n = 1;
+ datum->keys = xmalloc(sizeof *datum->keys);
+ datum->values = NULL;
+
+ error = ovsdb_atom_from_json(&datum->keys[0], type->key_type,
+ json, symtab);
+ if (error) {
+ free(datum->keys);
+ }
+ return error;
+ } else {
+ bool is_map = ovsdb_type_is_map(type);
+ const char *class = is_map ? "map" : "set";
+ const struct json *inner;
+ unsigned int i;
+ size_t n;
+
+ assert(is_map || ovsdb_type_is_set(type));
+
+ error = unwrap_json(json, class, JSON_ARRAY, &inner);
+ if (error) {
+ return error;
+ }
+
+ n = inner->u.array.n;
+ if (n < type->n_min || n > type->n_max) {
+ return ovsdb_syntax_error(json, NULL, "%s must have %u to "
+ "%u members but %zu are present",
+ class, type->n_min, type->n_max, n);
+ }
+
+ datum->n = 0;
+ datum->keys = xmalloc(n * sizeof *datum->keys);
+ datum->values = is_map ? xmalloc(n * sizeof *datum->values) : NULL;
+ for (i = 0; i < n; i++) {
+ const struct json *element = inner->u.array.elems[i];
+ const struct json *key = NULL;
+ const struct json *value = NULL;
+
+ if (!is_map) {
+ key = element;
+ } else {
+ error = parse_json_pair(element, &key, &value);
+ if (error) {
+ goto error;
+ }
+ }
+
+ error = ovsdb_atom_from_json(&datum->keys[i], type->key_type,
+ key, symtab);
+ if (error) {
+ goto error;
+ }
+
+ if (is_map) {
+ error = ovsdb_atom_from_json(&datum->values[i],
+ type->value_type, value, symtab);
+ if (error) {
+ ovsdb_atom_destroy(&datum->keys[i], type->key_type);
+ goto error;
+ }
+ }
+
+ datum->n++;
+ }
+
+ error = ovsdb_datum_sort(datum, type);
+ if (error) {
+ goto error;
+ }
+
+ return NULL;
+
+ error:
+ ovsdb_datum_destroy(datum, type);
+ return error;
+ }
+}
+
+struct json *
+ovsdb_datum_to_json(const struct ovsdb_datum *datum,
+ const struct ovsdb_type *type)
+{
+ /* These tests somewhat tolerate a 'datum' that does not exactly match
+ * 'type', in particular a datum with 'n' not in the allowed range. */
+ if (datum->n == 1 && ovsdb_type_is_scalar(type)) {
+ return ovsdb_atom_to_json(&datum->keys[0], type->key_type);
+ } else if (type->value_type == OVSDB_TYPE_VOID) {
+ struct json **elems;
+ size_t i;
+
+ elems = xmalloc(datum->n * sizeof *elems);
+ for (i = 0; i < datum->n; i++) {
+ elems[i] = ovsdb_atom_to_json(&datum->keys[i], type->key_type);
+ }
+
+ return wrap_json("set", json_array_create(elems, datum->n));
+ } else {
+ struct json **elems;
+ size_t i;
+
+ elems = xmalloc(datum->n * sizeof *elems);
+ for (i = 0; i < datum->n; i++) {
+ elems[i] = json_array_create_2(
+ ovsdb_atom_to_json(&datum->keys[i], type->key_type),
+ ovsdb_atom_to_json(&datum->values[i], type->value_type));
+ }
+
+ return wrap_json("map", json_array_create(elems, datum->n));
+ }
+}
+
+static uint32_t
+hash_atoms(enum ovsdb_atomic_type type, const union ovsdb_atom *atoms,
+ unsigned int n, uint32_t basis)
+{
+ if (type != OVSDB_TYPE_VOID) {
+ unsigned int i;
+
+ for (i = 0; i < n; i++) {
+ basis = ovsdb_atom_hash(&atoms[i], type, basis);
+ }
+ }
+ return basis;
+}
+
+uint32_t
+ovsdb_datum_hash(const struct ovsdb_datum *datum,
+ const struct ovsdb_type *type, uint32_t basis)
+{
+ basis = hash_atoms(type->key_type, datum->keys, datum->n, basis);
+ basis ^= (type->key_type << 24) | (type->value_type << 16) | datum->n;
+ basis = hash_atoms(type->value_type, datum->values, datum->n, basis);
+ return basis;
+}
+
+static int
+atom_arrays_compare_3way(const union ovsdb_atom *a,
+ const union ovsdb_atom *b,
+ enum ovsdb_atomic_type type,
+ size_t n)
+{
+ unsigned int i;
+
+ for (i = 0; i < n; i++) {
+ int cmp = ovsdb_atom_compare_3way(&a[i], &b[i], type);
+ if (cmp) {
+ return cmp;
+ }
+ }
+
+ return 0;
+}
+
+bool
+ovsdb_datum_equals(const struct ovsdb_datum *a,
+ const struct ovsdb_datum *b,
+ const struct ovsdb_type *type)
+{
+ return !ovsdb_datum_compare_3way(a, b, type);
+}
+
+int
+ovsdb_datum_compare_3way(const struct ovsdb_datum *a,
+ const struct ovsdb_datum *b,
+ const struct ovsdb_type *type)
+{
+ int cmp;
+
+ if (a->n != b->n) {
+ return a->n < b->n ? -1 : 1;
+ }
+
+ cmp = atom_arrays_compare_3way(a->keys, b->keys, type->key_type, a->n);
+ if (cmp) {
+ return cmp;
+ }
+
+ return (type->value_type == OVSDB_TYPE_VOID ? 0
+ : atom_arrays_compare_3way(a->values, b->values, type->value_type,
+ a->n));
+}
+
+static bool
+ovsdb_datum_contains(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);
+ 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));
+ }
+ }
+ return false;
+}
+
+/* Returns true if every element in 'a' is also in 'b', false otherwise. */
+bool
+ovsdb_datum_includes_all(const struct ovsdb_datum *a,
+ const struct ovsdb_datum *b,
+ const struct ovsdb_type *type)
+{
+ size_t i;
+
+ for (i = 0; i < a->n; i++) {
+ if (!ovsdb_datum_contains(a, i, b, type)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/* Returns true if no element in 'a' is also in 'b', false otherwise. */
+bool
+ovsdb_datum_excludes_all(const struct ovsdb_datum *a,
+ const struct ovsdb_datum *b,
+ const struct ovsdb_type *type)
+{
+ size_t i;
+
+ for (i = 0; i < a->n; i++) {
+ if (ovsdb_datum_contains(a, i, b, type)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+struct ovsdb_symbol_table {
+ struct shash sh;
+};
+
+struct ovsdb_symbol_table *
+ovsdb_symbol_table_create(void)
+{
+ struct ovsdb_symbol_table *symtab = xmalloc(sizeof *symtab);
+ shash_init(&symtab->sh);
+ return symtab;
+}
+
+void
+ovsdb_symbol_table_destroy(struct ovsdb_symbol_table *symtab)
+{
+ if (symtab) {
+ struct shash_node *node, *next;
+
+ SHASH_FOR_EACH_SAFE (node, next, &symtab->sh) {
+ free(node->data);
+ shash_delete(&symtab->sh, node);
+ }
+ shash_destroy(&symtab->sh);
+ free(symtab);
+ }
+}
+
+const struct uuid *
+ovsdb_symbol_table_get(const struct ovsdb_symbol_table *symtab,
+ const char *name)
+{
+ return shash_find_data(&symtab->sh, name);
+}
+
+void
+ovsdb_symbol_table_put(struct ovsdb_symbol_table *symtab, const char *name,
+ const struct uuid *uuid)
+{
+ struct uuid *entry = shash_find_data(&symtab->sh, name);
+ if (!entry) {
+ shash_add(&symtab->sh, name, xmemdup(uuid, sizeof *uuid));
+ } else {
+ *entry = *uuid;
+ }
+}
diff --git a/lib/ovsdb-data.h b/lib/ovsdb-data.h
new file mode 100644
index 00000000..35c4e307
--- /dev/null
+++ b/lib/ovsdb-data.h
@@ -0,0 +1,128 @@
+/* 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_DATA_H
+#define OVSDB_DATA_H 1
+
+#include <stdlib.h>
+#include "compiler.h"
+#include "ovsdb-types.h"
+
+struct ovsdb_symbol_table;
+
+/* One value of an atomic type (given by enum ovs_atomic_type). */
+union ovsdb_atom {
+ int64_t integer;
+ double real;
+ bool boolean;
+ char *string;
+ struct uuid uuid;
+};
+
+void ovsdb_atom_init_default(union ovsdb_atom *, enum ovsdb_atomic_type);
+void ovsdb_atom_clone(union ovsdb_atom *, const union ovsdb_atom *,
+ enum ovsdb_atomic_type);
+void ovsdb_atom_swap(union ovsdb_atom *, union ovsdb_atom *);
+
+static inline bool
+ovsdb_atom_needs_destruction(enum ovsdb_atomic_type type)
+{
+ return type == OVSDB_TYPE_STRING;
+}
+
+static inline void
+ovsdb_atom_destroy(union ovsdb_atom *atom, enum ovsdb_atomic_type type)
+{
+ if (type == OVSDB_TYPE_STRING) {
+ free(atom->string);
+ }
+}
+
+uint32_t ovsdb_atom_hash(const union ovsdb_atom *, enum ovsdb_atomic_type,
+ uint32_t basis);
+
+int ovsdb_atom_compare_3way(const union ovsdb_atom *,
+ const union ovsdb_atom *,
+ enum ovsdb_atomic_type);
+
+static inline bool ovsdb_atom_equals(const union ovsdb_atom *a,
+ const union ovsdb_atom *b,
+ enum ovsdb_atomic_type type)
+{
+ return !ovsdb_atom_compare_3way(a, b, type);
+}
+
+struct ovsdb_error *ovsdb_atom_from_json(union ovsdb_atom *,
+ enum ovsdb_atomic_type,
+ const struct json *,
+ const struct ovsdb_symbol_table *)
+ WARN_UNUSED_RESULT;
+struct json *ovsdb_atom_to_json(const union ovsdb_atom *,
+ enum ovsdb_atomic_type);
+
+/* One value of an OVSDB type (given by struct ovsdb_type). */
+struct ovsdb_datum {
+ unsigned int n; /* Number of 'keys' and 'values'. */
+ union ovsdb_atom *keys; /* Each of the ovsdb_type's 'key_type'. */
+ union ovsdb_atom *values; /* Each of the ovsdb_type's 'value_type'. */
+};
+
+void ovsdb_datum_init_default(struct ovsdb_datum *, const struct ovsdb_type *);
+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_from_json(struct ovsdb_datum *,
+ const struct ovsdb_type *,
+ const struct json *,
+ const struct ovsdb_symbol_table *)
+ WARN_UNUSED_RESULT;
+struct json *ovsdb_datum_to_json(const struct ovsdb_datum *,
+ const struct ovsdb_type *);
+
+uint32_t ovsdb_datum_hash(const struct ovsdb_datum *,
+ const struct ovsdb_type *, uint32_t basis);
+int ovsdb_datum_compare_3way(const struct ovsdb_datum *,
+ const struct ovsdb_datum *,
+ const struct ovsdb_type *);
+bool ovsdb_datum_equals(const struct ovsdb_datum *,
+ const struct ovsdb_datum *,
+ const struct ovsdb_type *);
+bool ovsdb_datum_includes_all(const struct ovsdb_datum *,
+ const struct ovsdb_datum *,
+ const struct ovsdb_type *);
+bool ovsdb_datum_excludes_all(const struct ovsdb_datum *,
+ const struct ovsdb_datum *,
+ const struct ovsdb_type *);
+
+static inline bool
+ovsdb_datum_conforms_to_type(const struct ovsdb_datum *datum,
+ const struct ovsdb_type *type)
+{
+ return datum->n >= type->n_min && datum->n <= type->n_max;
+}
+
+/* A table mapping from names to data items. Currently the data items are
+ * always UUIDs; perhaps this will be expanded in the future. */
+
+struct ovsdb_symbol_table *ovsdb_symbol_table_create(void);
+void ovsdb_symbol_table_destroy(struct ovsdb_symbol_table *);
+const struct uuid *ovsdb_symbol_table_get(const struct ovsdb_symbol_table *,
+ const char *name);
+void ovsdb_symbol_table_put(struct ovsdb_symbol_table *, const char *name,
+ const struct uuid *);
+
+#endif /* ovsdb-data.h */
diff --git a/lib/ovsdb-error.c b/lib/ovsdb-error.c
new file mode 100644
index 00000000..c0eddf28
--- /dev/null
+++ b/lib/ovsdb-error.c
@@ -0,0 +1,221 @@
+/* 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 "ovsdb-error.h"
+
+#include <inttypes.h>
+
+#include "backtrace.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "util.h"
+
+struct ovsdb_error {
+ const char *tag; /* String for "error" member. */
+ char *details; /* String for "details" member. */
+ char *syntax; /* String for "syntax" member. */
+ int errno_; /* Unix errno value, 0 if none. */
+};
+
+static struct ovsdb_error *
+ovsdb_error_valist(const char *tag, const char *details, va_list args)
+{
+ struct ovsdb_error *error = xmalloc(sizeof *error);
+ error->tag = tag ? tag : "ovsdb error";
+ error->details = details ? xvasprintf(details, args) : NULL;
+ error->syntax = NULL;
+ error->errno_ = 0;
+ return error;
+}
+
+struct ovsdb_error *
+ovsdb_error(const char *tag, const char *details, ...)
+{
+ struct ovsdb_error *error;
+ va_list args;
+
+ va_start(args, details);
+ error = ovsdb_error_valist(tag, details, args);
+ va_end(args);
+
+ return error;
+}
+
+struct ovsdb_error *
+ovsdb_io_error(int errno_, const char *details, ...)
+{
+ struct ovsdb_error *error;
+ va_list args;
+
+ va_start(args, details);
+ error = ovsdb_error_valist("I/O error", details, args);
+ va_end(args);
+
+ error->errno_ = errno_;
+
+ return error;
+}
+
+struct ovsdb_error *
+ovsdb_syntax_error(const struct json *json, const char *tag,
+ const char *details, ...)
+{
+ struct ovsdb_error *error;
+ va_list args;
+
+ va_start(args, details);
+ error = ovsdb_error_valist(tag ? tag : "syntax error", details, args);
+ va_end(args);
+
+ if (json) {
+ /* XXX this is much too much information in some cases */
+ error->syntax = json_to_string(json, 0);
+ }
+
+ return error;
+}
+
+struct ovsdb_error *
+ovsdb_wrap_error(struct ovsdb_error *error, const char *details, ...)
+{
+ va_list args;
+ char *msg;
+
+ va_start(args, details);
+ msg = xvasprintf(details, args);
+ va_end(args);
+
+ if (error->details) {
+ char *new = xasprintf("%s: %s", msg, error->details);
+ free(error->details);
+ error->details = new;
+ free(msg);
+ } else {
+ error->details = msg;
+ }
+
+ return error;
+}
+
+struct ovsdb_error *
+ovsdb_internal_error(const char *file, int line, const char *details, ...)
+{
+ struct ds ds = DS_EMPTY_INITIALIZER;
+ struct backtrace backtrace;
+ struct ovsdb_error *error;
+ va_list args;
+
+ ds_put_format(&ds, "%s:%d:", file, line);
+
+ if (details) {
+ ds_put_char(&ds, ' ');
+ va_start(args, details);
+ ds_put_format_valist(&ds, details, args);
+ va_end(args);
+ }
+
+ backtrace_capture(&backtrace);
+ if (backtrace.n_frames) {
+ int i;
+
+ ds_put_cstr(&ds, " (backtrace:");
+ for (i = 0; i < backtrace.n_frames; i++) {
+ ds_put_format(&ds, " 0x%08"PRIxPTR, backtrace.frames[i]);
+ }
+ ds_put_char(&ds, ')');
+ }
+
+ ds_put_format(&ds, " (%s %s%s)", program_name, VERSION, BUILDNR);
+
+ error = ovsdb_error("internal error", "%s", ds_cstr(&ds));
+
+ ds_destroy(&ds);
+
+ return error;
+}
+
+void
+ovsdb_error_destroy(struct ovsdb_error *error)
+{
+ if (error) {
+ free(error->details);
+ free(error->syntax);
+ free(error);
+ }
+}
+
+struct ovsdb_error *
+ovsdb_error_clone(const struct ovsdb_error *old)
+{
+ if (old) {
+ struct ovsdb_error *new = xmalloc(sizeof *new);
+ new->tag = old->tag;
+ new->details = old->details ? xstrdup(old->details) : NULL;
+ new->syntax = old->syntax ? xstrdup(old->syntax) : NULL;
+ new->errno_ = old->errno_;
+ return new;
+ } else {
+ return NULL;
+ }
+}
+
+static const char *
+ovsdb_errno_string(int error)
+{
+ return error == EOF ? "unexpected end of file" : strerror(error);
+}
+
+struct json *
+ovsdb_error_to_json(const struct ovsdb_error *error)
+{
+ struct json *json = json_object_create();
+ json_object_put_string(json, "error", error->tag);
+ if (error->details) {
+ json_object_put_string(json, "details", error->details);
+ }
+ if (error->syntax) {
+ json_object_put_string(json, "syntax", error->syntax);
+ }
+ if (error->errno_) {
+ json_object_put_string(json, "io-error",
+ ovsdb_errno_string(error->errno_));
+ }
+ return json;
+}
+
+char *
+ovsdb_error_to_string(const struct ovsdb_error *error)
+{
+ struct ds ds = DS_EMPTY_INITIALIZER;
+ if (error->syntax) {
+ ds_put_format(&ds, "syntax \"%s\": ", error->syntax);
+ }
+ ds_put_cstr(&ds, error->tag);
+ if (error->details) {
+ ds_put_format(&ds, ": %s", error->details);
+ }
+ if (error->errno_) {
+ ds_put_format(&ds, " (%s)", ovsdb_errno_string(error->errno_));
+ }
+ return ds_steal_cstr(&ds);
+}
+
+const char *
+ovsdb_error_get_tag(const struct ovsdb_error *error)
+{
+ return error->tag;
+}
diff --git a/lib/ovsdb-error.h b/lib/ovsdb-error.h
new file mode 100644
index 00000000..7e2523e3
--- /dev/null
+++ b/lib/ovsdb-error.h
@@ -0,0 +1,53 @@
+/* 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_ERROR_H
+#define OVSDB_ERROR_H 1
+
+#include "compiler.h"
+
+struct json;
+
+struct ovsdb_error *ovsdb_error(const char *tag, const char *details, ...)
+ PRINTF_FORMAT(2, 3)
+ WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_io_error(int error, const char *details, ...)
+ PRINTF_FORMAT(2, 3)
+ WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_syntax_error(const struct json *, const char *tag,
+ const char *details, ...)
+ PRINTF_FORMAT(3, 4)
+ WARN_UNUSED_RESULT;
+
+struct ovsdb_error *ovsdb_wrap_error(struct ovsdb_error *error,
+ const char *details, ...)
+ PRINTF_FORMAT(2, 3);
+
+struct ovsdb_error *ovsdb_internal_error(const char *file, int line,
+ const char *details, ...)
+ PRINTF_FORMAT(3, 4)
+ WARN_UNUSED_RESULT;
+#define OVSDB_BUG(MSG) ovsdb_internal_error(__FILE__, __LINE__, "%s", MSG)
+
+void ovsdb_error_destroy(struct ovsdb_error *);
+struct ovsdb_error *ovsdb_error_clone(const struct ovsdb_error *)
+ WARN_UNUSED_RESULT;
+
+char *ovsdb_error_to_string(const struct ovsdb_error *);
+struct json *ovsdb_error_to_json(const struct ovsdb_error *);
+
+const char *ovsdb_error_get_tag(const struct ovsdb_error *);
+
+#endif /* ovsdb-error.h */
diff --git a/lib/ovsdb-parser.c b/lib/ovsdb-parser.c
new file mode 100644
index 00000000..d923d21a
--- /dev/null
+++ b/lib/ovsdb-parser.c
@@ -0,0 +1,167 @@
+/* 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 "ovsdb-parser.h"
+
+#include <ctype.h>
+#include <stdarg.h>
+
+#include "ovsdb-error.h"
+
+void
+ovsdb_parser_init(struct ovsdb_parser *parser, const struct json *json,
+ const char *name, ...)
+{
+ va_list args;
+
+ va_start(args, name);
+ parser->name = xvasprintf(name, args);
+ va_end(args);
+
+ svec_init(&parser->used);
+ parser->error = NULL;
+
+ parser->json = (json && json->type == JSON_OBJECT ? json : NULL);
+ if (!parser->json) {
+ ovsdb_parser_raise_error(parser, "Object expected.");
+ }
+}
+
+static bool
+is_id(const char *string)
+{
+ unsigned char c;
+
+ c = *string;
+ if (!isalpha(c) && c != '_') {
+ return false;
+ }
+
+ for (;;) {
+ c = *++string;
+ if (c == '\0') {
+ return true;
+ } else if (!isalpha(c) && !isdigit(c) && c != '_') {
+ return false;
+ }
+ }
+}
+
+const struct json *
+ovsdb_parser_member(struct ovsdb_parser *parser, const char *name,
+ enum ovsdb_parser_types types)
+{
+ struct json *value;
+
+ if (!parser->json) {
+ return NULL;
+ }
+
+ value = shash_find_data(json_object(parser->json), name);
+ if (!value) {
+ if (!(types & OP_OPTIONAL)) {
+ ovsdb_parser_raise_error(parser,
+ "Required '%s' member is missing.", name);
+ }
+ return NULL;
+ }
+
+ if (value->type >= 0 && value->type < JSON_N_TYPES
+ && (types & (1u << value->type)
+ || (types & OP_ID
+ && value->type == JSON_STRING
+ && is_id(value->u.string))))
+ {
+ svec_add(&parser->used, name);
+ return value;
+ } else {
+ ovsdb_parser_raise_error(parser, "Type mismatch for member '%s'.",
+ name);
+ return NULL;
+ }
+}
+
+void
+ovsdb_parser_raise_error(struct ovsdb_parser *parser, const char *format, ...)
+{
+ if (!parser->error) {
+ struct ovsdb_error *error;
+ va_list args;
+ char *message;
+
+ va_start(args, format);
+ message = xvasprintf(format, args);
+ va_end(args);
+
+ error = ovsdb_syntax_error(parser->json, NULL, "Parsing %s failed: %s",
+ parser->name, message);
+ free(message);
+
+ parser->error = error;
+ }
+}
+
+struct ovsdb_error *
+ovsdb_parser_get_error(const struct ovsdb_parser *parser)
+{
+ return parser->error ? ovsdb_error_clone(parser->error) : NULL;
+}
+
+bool
+ovsdb_parser_has_error(const struct ovsdb_parser *parser)
+{
+ return parser->error != NULL;
+}
+
+struct ovsdb_error *
+ovsdb_parser_finish(struct ovsdb_parser *parser)
+{
+ if (!parser->error) {
+ const struct shash *object = json_object(parser->json);
+ size_t n_unused;
+
+ /* XXX this method of detecting unused members can be made cheaper */
+ svec_sort_unique(&parser->used);
+ n_unused = shash_count(object) - parser->used.n;
+ if (n_unused) {
+ struct shash_node *node;
+
+ SHASH_FOR_EACH (node, object) {
+ if (!svec_contains(&parser->used, node->name)) {
+ if (n_unused > 1) {
+ ovsdb_parser_raise_error(
+ parser,
+ "Member '%s' and %zu other member%s "
+ "are present but not allowed here.",
+ node->name, n_unused - 1, n_unused > 2 ? "s" : "");
+ } else {
+ ovsdb_parser_raise_error(
+ parser,
+ "Member '%s' is present but not allowed here.",
+ node->name);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ free(parser->name);
+ svec_destroy(&parser->used);
+
+ return parser->error;
+}
diff --git a/lib/ovsdb-parser.h b/lib/ovsdb-parser.h
new file mode 100644
index 00000000..f9a2ef7e
--- /dev/null
+++ b/lib/ovsdb-parser.h
@@ -0,0 +1,74 @@
+/* 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_PARSER_H
+#define OVSDB_PARSER_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+#include "json.h"
+#include "svec.h"
+#include "util.h"
+
+struct ovsdb_parser {
+ char *name; /* Used only in error messages. */
+ struct svec used; /* Already-parsed names from 'object'. */
+ const struct json *json; /* JSON object being parsed. */
+ struct ovsdb_error *error; /* Error signaled, if any. */
+};
+
+/* Check that the JSON types make the bitwise tricks below work OK. */
+BUILD_ASSERT_DECL(JSON_NULL >= 0 && JSON_NULL < 10);
+BUILD_ASSERT_DECL(JSON_FALSE >= 0 && JSON_FALSE < 10);
+BUILD_ASSERT_DECL(JSON_TRUE >= 0 && JSON_TRUE < 10);
+BUILD_ASSERT_DECL(JSON_OBJECT >= 0 && JSON_OBJECT < 10);
+BUILD_ASSERT_DECL(JSON_ARRAY >= 0 && JSON_ARRAY < 10);
+BUILD_ASSERT_DECL(JSON_INTEGER >= 0 && JSON_INTEGER < 10);
+BUILD_ASSERT_DECL(JSON_REAL >= 0 && JSON_REAL < 10);
+BUILD_ASSERT_DECL(JSON_STRING >= 0 && JSON_STRING < 10);
+BUILD_ASSERT_DECL(JSON_N_TYPES == 8);
+
+enum ovsdb_parser_types {
+ OP_NULL = 1 << JSON_NULL, /* null */
+ OP_FALSE = 1 << JSON_FALSE, /* false */
+ OP_TRUE = 1 << JSON_TRUE, /* true */
+ OP_OBJECT = 1 << JSON_OBJECT, /* {"a": b, "c": d, ...} */
+ OP_ARRAY = 1 << JSON_ARRAY, /* [1, 2, 3, ...] */
+ OP_INTEGER = 1 << JSON_INTEGER, /* 123. */
+ OP_NONINTEGER = 1 << JSON_REAL, /* 123.456. */
+ OP_STRING = 1 << JSON_STRING, /* "..." */
+
+ OP_BOOLEAN = OP_FALSE | OP_TRUE,
+ OP_NUMBER = OP_INTEGER | OP_NONINTEGER,
+
+ OP_ID = 1 << JSON_N_TYPES, /* "[_a-zA-Z][_a-zA-Z0-9]*" */
+ OP_OPTIONAL = 1 << (JSON_N_TYPES + 1) /* no value at all */
+};
+
+void ovsdb_parser_init(struct ovsdb_parser *, const struct json *,
+ const char *name, ...)
+ PRINTF_FORMAT(3, 4);
+const struct json *ovsdb_parser_member(struct ovsdb_parser *, const char *name,
+ enum ovsdb_parser_types);
+
+void ovsdb_parser_raise_error(struct ovsdb_parser *parser,
+ const char *format, ...)
+ PRINTF_FORMAT(2, 3);
+bool ovsdb_parser_has_error(const struct ovsdb_parser *);
+struct ovsdb_error *ovsdb_parser_get_error(const struct ovsdb_parser *);
+struct ovsdb_error *ovsdb_parser_finish(struct ovsdb_parser *)
+ WARN_UNUSED_RESULT;
+
+#endif /* ovsdb-parser.h */
diff --git a/lib/ovsdb-types.c b/lib/ovsdb-types.c
new file mode 100644
index 00000000..07982e34
--- /dev/null
+++ b/lib/ovsdb-types.c
@@ -0,0 +1,251 @@
+/* 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 "ovsdb-types.h"
+
+#include <limits.h>
+
+#include "dynamic-string.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+
+const struct ovsdb_type ovsdb_type_integer =
+ OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_INTEGER);
+const struct ovsdb_type ovsdb_type_real =
+ OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_REAL);
+const struct ovsdb_type ovsdb_type_boolean =
+ OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_BOOLEAN);
+const struct ovsdb_type ovsdb_type_string =
+ OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_STRING);
+const struct ovsdb_type ovsdb_type_uuid =
+ OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_UUID);
+
+const char *
+ovsdb_atomic_type_to_string(enum ovsdb_atomic_type type)
+{
+ switch (type) {
+ case OVSDB_TYPE_VOID:
+ return "void";
+
+ case OVSDB_TYPE_INTEGER:
+ return "integer";
+
+ case OVSDB_TYPE_REAL:
+ return "real";
+
+ case OVSDB_TYPE_BOOLEAN:
+ return "boolean";
+
+ case OVSDB_TYPE_STRING:
+ return "string";
+
+ case OVSDB_TYPE_UUID:
+ return "uuid";
+
+ case OVSDB_N_TYPES:
+ default:
+ return "<invalid>";
+ }
+}
+
+struct json *
+ovsdb_atomic_type_to_json(enum ovsdb_atomic_type type)
+{
+ return json_string_create(ovsdb_atomic_type_to_string(type));
+}
+
+bool
+ovsdb_type_is_valid(const struct ovsdb_type *type)
+{
+ return (type->key_type != OVSDB_TYPE_VOID
+ && ovsdb_atomic_type_is_valid(type->key_type)
+ && ovsdb_atomic_type_is_valid(type->value_type)
+ && type->n_min <= type->n_max
+ && (type->value_type == OVSDB_TYPE_VOID
+ || ovsdb_atomic_type_is_valid_key(type->key_type)));
+}
+
+bool
+ovsdb_atomic_type_from_string(const char *string, enum ovsdb_atomic_type *type)
+{
+ if (!strcmp(string, "integer")) {
+ *type = OVSDB_TYPE_INTEGER;
+ } else if (!strcmp(string, "real")) {
+ *type = OVSDB_TYPE_REAL;
+ } else if (!strcmp(string, "boolean")) {
+ *type = OVSDB_TYPE_BOOLEAN;
+ } else if (!strcmp(string, "string")) {
+ *type = OVSDB_TYPE_STRING;
+ } else if (!strcmp(string, "uuid")) {
+ *type = OVSDB_TYPE_UUID;
+ } else {
+ return false;
+ }
+ return true;
+}
+
+struct ovsdb_error *
+ovsdb_atomic_type_from_json(enum ovsdb_atomic_type *type,
+ const struct json *json)
+{
+ if (json->type == JSON_STRING) {
+ if (ovsdb_atomic_type_from_string(json_string(json), type)) {
+ return NULL;
+ } else {
+ *type = OVSDB_TYPE_VOID;
+ return ovsdb_syntax_error(json, NULL,
+ "\"%s\" is not an atomic-type",
+ json_string(json));
+ }
+ } else {
+ *type = OVSDB_TYPE_VOID;
+ return ovsdb_syntax_error(json, NULL, "atomic-type expected");
+ }
+}
+
+static struct ovsdb_error *
+n_from_json(const struct json *json, unsigned int *n)
+{
+ if (!json) {
+ return NULL;
+ } else if (json->type == JSON_INTEGER
+ && json->u.integer >= 0 && json->u.integer < UINT_MAX) {
+ *n = json->u.integer;
+ return NULL;
+ } else {
+ return ovsdb_syntax_error(json, NULL, "bad min or max value");
+ }
+}
+
+char *
+ovsdb_type_to_english(const struct ovsdb_type *type)
+{
+ const char *key = ovsdb_atomic_type_to_string(type->key_type);
+ const char *value = ovsdb_atomic_type_to_string(type->value_type);
+ if (ovsdb_type_is_scalar(type)) {
+ return xstrdup(key);
+ } else {
+ struct ds s = DS_EMPTY_INITIALIZER;
+ ds_put_cstr(&s, ovsdb_type_is_set(type) ? "set" : "map");
+ if (type->n_max == UINT_MAX) {
+ if (type->n_min) {
+ ds_put_format(&s, " of %u or more", type->n_min);
+ } else {
+ ds_put_cstr(&s, " of");
+ }
+ } else if (type->n_min) {
+ ds_put_format(&s, " of %u to %u", type->n_min, type->n_max);
+ } else {
+ ds_put_format(&s, " of up to %u", type->n_max);
+ }
+ if (ovsdb_type_is_set(type)) {
+ ds_put_format(&s, " %ss", key);
+ } else {
+ ds_put_format(&s, " (%s, %s) pairs", key, value);
+ }
+ return ds_cstr(&s);
+ }
+}
+
+struct ovsdb_error *
+ovsdb_type_from_json(struct ovsdb_type *type, const struct json *json)
+{
+ type->value_type = OVSDB_TYPE_VOID;
+ type->n_min = 1;
+ type->n_max = 1;
+
+ if (json->type == JSON_STRING) {
+ return ovsdb_atomic_type_from_json(&type->key_type, json);
+ } else if (json->type == JSON_OBJECT) {
+ const struct json *key, *value, *min, *max;
+ struct ovsdb_error *error;
+ struct ovsdb_parser parser;
+
+ ovsdb_parser_init(&parser, json, "ovsdb type");
+ key = ovsdb_parser_member(&parser, "key", OP_STRING);
+ value = ovsdb_parser_member(&parser, "value", OP_STRING | OP_OPTIONAL);
+ min = ovsdb_parser_member(&parser, "min", OP_INTEGER | OP_OPTIONAL);
+ max = ovsdb_parser_member(&parser, "max",
+ OP_INTEGER | OP_STRING | OP_OPTIONAL);
+ error = ovsdb_parser_finish(&parser);
+ if (error) {
+ return error;
+ }
+
+ error = ovsdb_atomic_type_from_json(&type->key_type, key);
+ if (error) {
+ return error;
+ }
+
+ if (value) {
+ error = ovsdb_atomic_type_from_json(&type->value_type, value);
+ if (error) {
+ return error;
+ }
+ }
+
+ error = n_from_json(min, &type->n_min);
+ if (error) {
+ return error;
+ }
+
+ if (max && max->type == JSON_STRING
+ && !strcmp(max->u.string, "unlimited")) {
+ type->n_max = UINT_MAX;
+ } else {
+ error = n_from_json(max, &type->n_max);
+ if (error) {
+ return error;
+ }
+ }
+
+ if (!ovsdb_type_is_valid(type)) {
+ return ovsdb_syntax_error(json, NULL,
+ "ovsdb type fails constraint checks");
+ }
+
+ return NULL;
+ } else {
+ return ovsdb_syntax_error(json, NULL, "ovsdb type expected");
+ }
+}
+
+struct json *
+ovsdb_type_to_json(const struct ovsdb_type *type)
+{
+ if (ovsdb_type_is_scalar(type)) {
+ return ovsdb_atomic_type_to_json(type->key_type);
+ } else {
+ struct json *json = json_object_create();
+ json_object_put(json, "key",
+ ovsdb_atomic_type_to_json(type->key_type));
+ if (type->value_type != OVSDB_TYPE_VOID) {
+ json_object_put(json, "value",
+ ovsdb_atomic_type_to_json(type->value_type));
+ }
+ if (type->n_min != 1) {
+ json_object_put(json, "min", json_integer_create(type->n_min));
+ }
+ if (type->n_max == UINT_MAX) {
+ json_object_put_string(json, "max", "unlimited");
+ } else if (type->n_max != 1) {
+ json_object_put(json, "max", json_integer_create(type->n_max));
+ }
+ return json;
+ }
+}
diff --git a/lib/ovsdb-types.h b/lib/ovsdb-types.h
new file mode 100644
index 00000000..78d76c97
--- /dev/null
+++ b/lib/ovsdb-types.h
@@ -0,0 +1,126 @@
+/* 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_TYPES_H
+#define OVSDB_TYPES_H 1
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "compiler.h"
+#include "uuid.h"
+
+struct json;
+
+/* An atomic type: one that OVSDB regards as a single unit of data. */
+enum ovsdb_atomic_type {
+ OVSDB_TYPE_VOID, /* No value. */
+ OVSDB_TYPE_INTEGER, /* Signed 64-bit integer. */
+ OVSDB_TYPE_REAL, /* IEEE 754 double-precision floating point. */
+ OVSDB_TYPE_BOOLEAN, /* True or false. */
+ OVSDB_TYPE_STRING, /* UTF-8 string. */
+ OVSDB_TYPE_UUID, /* RFC 4122 UUID referencing a table row. */
+ OVSDB_N_TYPES
+};
+
+static inline bool ovsdb_atomic_type_is_valid(enum ovsdb_atomic_type);
+static inline bool ovsdb_atomic_type_is_valid_key(enum ovsdb_atomic_type);
+bool ovsdb_atomic_type_from_string(const char *, enum ovsdb_atomic_type *);
+struct ovsdb_error *ovsdb_atomic_type_from_json(enum ovsdb_atomic_type *,
+ const struct json *);
+const char *ovsdb_atomic_type_to_string(enum ovsdb_atomic_type);
+struct json *ovsdb_atomic_type_to_json(enum ovsdb_atomic_type);
+
+/* An OVSDB type. One of:
+ *
+ * - An atomic type.
+ *
+ * - A set of atomic types.
+ *
+ * - A map from one atomic type to another.
+ */
+struct ovsdb_type {
+ enum ovsdb_atomic_type key_type;
+ enum ovsdb_atomic_type value_type;
+ unsigned int n_min;
+ unsigned int n_max; /* UINT_MAX stands in for "unlimited". */
+};
+
+#define OVSDB_TYPE_SCALAR_INITIALIZER(KEY_TYPE) \
+ { KEY_TYPE, OVSDB_TYPE_VOID, 1, 1 }
+
+extern const struct ovsdb_type ovsdb_type_integer;
+extern const struct ovsdb_type ovsdb_type_real;
+extern const struct ovsdb_type ovsdb_type_boolean;
+extern const struct ovsdb_type ovsdb_type_string;
+extern const struct ovsdb_type ovsdb_type_uuid;
+
+bool ovsdb_type_is_valid(const struct ovsdb_type *);
+
+static inline bool ovsdb_type_is_scalar(const struct ovsdb_type *);
+static inline bool ovsdb_type_is_optional(const struct ovsdb_type *);
+static inline bool ovsdb_type_is_composite(const struct ovsdb_type *);
+static inline bool ovsdb_type_is_set(const struct ovsdb_type *);
+static inline bool ovsdb_type_is_map(const struct ovsdb_type *);
+
+char *ovsdb_type_to_english(const struct ovsdb_type *);
+
+struct ovsdb_error *ovsdb_type_from_json(struct ovsdb_type *,
+ const struct json *)
+ WARN_UNUSED_RESULT;
+struct json *ovsdb_type_to_json(const struct ovsdb_type *);
+
+/* Inline function implementations. */
+
+static inline bool
+ovsdb_atomic_type_is_valid(enum ovsdb_atomic_type atomic_type)
+{
+ return atomic_type >= 0 && atomic_type < OVSDB_N_TYPES;
+}
+
+static inline bool
+ovsdb_atomic_type_is_valid_key(enum ovsdb_atomic_type atomic_type)
+{
+ /* XXX should we disallow reals or booleans as keys? */
+ return ovsdb_atomic_type_is_valid(atomic_type);
+}
+
+static inline bool ovsdb_type_is_scalar(const struct ovsdb_type *type)
+{
+ return (type->value_type == OVSDB_TYPE_VOID
+ && type->n_min == 1 && type->n_max == 1);
+}
+
+static inline bool ovsdb_type_is_optional(const struct ovsdb_type *type)
+{
+ return type->n_min == 0;
+}
+
+static inline bool ovsdb_type_is_composite(const struct ovsdb_type *type)
+{
+ return type->n_max > 1;
+}
+
+static inline bool ovsdb_type_is_set(const struct ovsdb_type *type)
+{
+ return (type->value_type == OVSDB_TYPE_VOID
+ && (type->n_min != 1 || type->n_max != 1));
+}
+
+static inline bool ovsdb_type_is_map(const struct ovsdb_type *type)
+{
+ return type->value_type != OVSDB_TYPE_VOID;
+}
+
+#endif /* ovsdb-types.h */
diff --git a/lib/sort.c b/lib/sort.c
new file mode 100644
index 00000000..017b0a9a
--- /dev/null
+++ b/lib/sort.c
@@ -0,0 +1,70 @@
+/* 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 "sort.h"
+
+#include "random.h"
+
+static size_t
+partition(size_t p, size_t r,
+ int (*compare)(size_t a, size_t b, void *aux),
+ void (*swap)(size_t a, size_t b, void *aux),
+ void *aux)
+{
+ size_t x = r - 1;
+ size_t i, j;
+
+ i = p;
+ for (j = p; j < x; j++) {
+ if (compare(j, x, aux) <= 0) {
+ swap(i++, j, aux);
+ }
+ }
+ swap(i, x, aux);
+ return i;
+}
+
+static void
+quicksort(size_t p, size_t r,
+ int (*compare)(size_t a, size_t b, void *aux),
+ void (*swap)(size_t a, size_t b, void *aux),
+ void *aux)
+{
+ size_t i, q;
+
+ if (r - p < 2) {
+ return;
+ }
+
+ i = random_range(r - p) + p;
+ if (r - 1 != i) {
+ swap(r - 1, i, aux);
+ }
+
+ q = partition(p, r, compare, swap, aux);
+ quicksort(p, q, compare, swap, aux);
+ quicksort(q, r, compare, swap, aux);
+}
+
+void
+sort(size_t count,
+ int (*compare)(size_t a, size_t b, void *aux),
+ void (*swap)(size_t a, size_t b, void *aux),
+ void *aux)
+{
+ quicksort(0, count, compare, swap, aux);
+}
diff --git a/lib/sort.h b/lib/sort.h
new file mode 100644
index 00000000..c952f444
--- /dev/null
+++ b/lib/sort.h
@@ -0,0 +1,26 @@
+/* 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 SORT_H
+#define SORT_H 1
+
+#include <stddef.h>
+
+void sort(size_t count,
+ int (*compare)(size_t a, size_t b, void *aux),
+ void (*swap)(size_t a, size_t b, void *aux),
+ void *aux);
+
+#endif /* sort.h */
diff --git a/lib/vlog-modules.def b/lib/vlog-modules.def
index 684954ea..d5a59ab0 100644
--- a/lib/vlog-modules.def
+++ b/lib/vlog-modules.def
@@ -54,6 +54,11 @@ VLOG_MODULE(ofctl)
VLOG_MODULE(ovs_discover)
VLOG_MODULE(ofproto)
VLOG_MODULE(openflowd)
+VLOG_MODULE(ovsdb)
+VLOG_MODULE(ovsdb_file)
+VLOG_MODULE(ovsdb_jsonrpc_server)
+VLOG_MODULE(ovsdb_server)
+VLOG_MODULE(ovsdb_tool)
VLOG_MODULE(pktbuf)
VLOG_MODULE(pcap)
VLOG_MODULE(poll_loop)
diff --git a/ovsdb/SPECS b/ovsdb/SPECS
new file mode 100644
index 00000000..12d97682
--- /dev/null
+++ b/ovsdb/SPECS
@@ -0,0 +1,628 @@
+ ===================================================
+ Open vSwitch Configuration Database Specification
+ ===================================================
+
+Basic Notation
+--------------
+
+The descriptions below use the following shorthand notations for JSON
+values. Additional notation is presented later.
+
+<string>
+
+ A JSON string.
+
+<id>
+
+ A JSON string matching [a-zA-Z_][a-zA-Z0-9_]*.
+
+ <id>s that begin with _ are reserved to the implementation and may
+ not be used by the user.
+
+<boolean>
+
+ A JSON true or false value.
+
+<number>
+
+ A JSON number.
+
+<integer>
+
+ A JSON number with an integer value, within a certain range
+ (currently -2**63...+2**63-1).
+
+Schema Format
+-------------
+
+An Open vSwitch configuration database consists of a set of tables,
+each of which has a number of columns and zero or more rows. A schema
+is represented by <database-schema>, as described below.
+
+<database-schema>
+
+ A JSON object with the following members:
+
+ "name": <id> required
+ "comment": <string> optional
+ "tables": {<id>: <table-schema>, ...} required
+
+ The "name" identifies the database as a whole. The "comment"
+ optionally provides more information about the database. The
+ value of "tables" is a JSON object whose names are table names and
+ whose values are <table-schema>s.
+
+<table-schema>
+
+ A JSON object with the following members:
+
+ "comment": <string> optional
+ "columns": {<id>: <column-schema>, ...} required
+
+ The "comment" optionally provides information about this table for
+ a human reader. The value of "tables" is a JSON object whose
+ names are table names and whose values are <column-schema>s.
+
+ Every table has the following columns whose definitions are not
+ included in the schema:
+
+ "_uuid": This column, which contains exactly one UUID value,
+ is initialized to a random value by the database engine when
+ it creates a row. It is read-only, and its value never
+ changes during the lifetime of a row.
+
+ "_version": Like "_uuid", this column contains exactly one
+ UUID value, initialized to a random value by the database
+ engine when it creates a row, and it is read-only. However,
+ its value changes to a new random value whenever any other
+ field in the row changes. Furthermore, its value is
+ ephemeral: when the database is closed and reopened, or when
+ the database process is stopped and then started again, each
+ "_version" also changes to a new random value.
+
+<column-schema>
+
+ A JSON object with the following members:
+
+ "comment": <string> optional
+ "type": <type> required
+ "ephemeral": <boolean> optional
+
+ The "comment" optionally provides information about this column
+ for a human reader. The "type" specifies the type of data stored
+ in this column. If "ephemeral" is specified as true, then this
+ column's values are not guaranteed to be durable; they may be lost
+ when the database restarts.
+
+<type>
+
+ The type of a database column. Either an <atomic-type> or a JSON
+ object that describes the type of a database column, with the
+ following members:
+
+ "key": <atomic-type> required
+ "value": <atomic-type> optional
+ "min": <integer> optional
+ "max": <integer> or "unlimited" optional
+
+ If "min" or "max" is not specified, each defaults to 1. If "max"
+ is specified as "unlimited", then there is no specified maximum
+ number of elements, although the implementation will enforce some
+ limit. After considering defaults, "min" must be at least 0,
+ "max" must be at least 1, and "max" must be greater than or equal
+ to "min".
+
+ If "min" and "max" are both 1 and "value" is not specified, the
+ type is the scalar type specified by "key".
+
+ If "min" is not 1 or "max" is not 1, or both, and "value" is not
+ specified, the type is a set of scalar type "key".
+
+ If "value" is specified, the type is a map from type "key" to type
+ "value".
+
+<atomic-type>
+
+ One of the strings "integer", "real", "boolean", "string", or
+ "uuid", representing the specified scalar type.
+
+Wire Protocol
+-------------
+
+The database wire protocol is implemented in JSON-RPC 1.0. It
+consists of the following JSON-RPC methods:
+
+get_schema
+..........
+
+Request object members:
+
+ "method": "get_schema" required
+ "params": [] required
+ "id": any JSON value except null required
+
+Response object members:
+
+ "result": <database-schema>
+ "error": null
+ "id": same "id" as request
+
+This operation retrieves a <database-schema> that describes the
+hosted database.
+
+transact
+........
+
+Request object members:
+
+ "method": "transact" required
+ "params": [<operation>*] required
+ "id": any JSON value except null required
+
+Response object members:
+
+ "result": [<object>*]
+ "error": null
+ "id": same "id" as request
+
+The "params" array for this method consists of zero or more JSON
+objects, each of which represents a single database operation. The
+"Operations" section below describes the valid operations.
+
+The value of "id" must be unique among all in-flight transactions
+within the current JSON-RPC session. Otherwise, the server may return
+a JSON-RPC error.
+
+The database server executes each of the specified operations in the
+specified order, except that if an operation fails, then the remaining
+operations are not executed.
+
+The set of operations is executed as a single atomic, consistent,
+isolated transaction. The transaction is committed only if every
+operation succeeds. Durability of the commit is not guaranteed unless
+the "commit" operation, with "durable" set to true, is included in the
+operation set (see below).
+
+Regardless of whether errors occur, the response is always a JSON-RPC
+response with null "error" and a "result" member that is an array with
+the same number of elements as "params". Each element of the "result"
+array corresponds to the same element of the "params" array. The
+"result" array elements may be interpreted as follows:
+
+ - A JSON object that does not contain an "error" member indicates
+ that the operation completed successfully. The specific members
+ of the object are specified below in the descriptions of
+ individual operations. Some operations do not produce any
+ results, in which case the object will have no members.
+
+ - A JSON object that contains a "error" member indicates that the
+ operation completed with an error. The value of the "error"
+ member is a short string, specified in this document, that
+ broadly indicates the class of the error. Besides the ones
+ listed for a specific operation, any operation may result in one
+ the following "error"s:
+
+ "error": "resources exhausted"
+
+ The operation or the transaction requires more resources
+ (memory, disk, CPU, etc.) than are currently available to
+ the database server.
+
+ "error": "syntax error"
+
+ The operation is not specified correctly: a required request
+ object member is missing, an unknown or unsupported request
+ object member is present, the operation attempts to act on a
+ table that does not exist, the operation modifies a
+ read-only table column, etc.
+
+ Database implementations may use "error" strings not specified
+ in this document to indicate errors that do not fit into any of
+ the specified categories.
+
+ Optionally, the object may include a "details" member, whose
+ value is a string that describes the error in more detail for
+ the benefit of a human user or administrator. The object may
+ also have other members that describe the error in more detail.
+ This document does not specify the names or values of these
+ members.
+
+ - A JSON null value indicates that the operation was not attempted
+ because a prior operation failed.
+
+In general, "result" contains some number of successful results,
+possibly followed by an error, in turn followed by enough JSON null
+values to match the number of elements in "params". There is one
+exception: if all of the operations succeed, but the results cannot be
+committed (e.g. due to I/O errors), then "result" will have one more
+element than "params", with the additional element describing the
+error.
+
+If "params" contains one or more "wait" operations, then the
+transaction may take an arbitrary amount of time to complete. The
+database implementation must be capable of accepting, executing, and
+replying to other transactions and other JSON-RPC requests while a
+transaction or transactions containing "wait" operations are
+outstanding on the same or different JSON-RPC sessions.
+
+The section "Notation for the Wire Protocol" below describes
+additional notation for use with the wire protocol. After that, the
+"Operations" section describes each operation.
+
+cancel
+......
+
+Request object members:
+
+ "method": "cancel" required
+ "params": the "id" for an outstanding request required
+ "id": null required
+
+Response object members:
+
+ <no response>
+
+This JSON-RPC notification instructs the database server to
+immediately complete or cancel the "transact" request whose "id" is
+the same as the notification's "params" value.
+
+If the "transact" request can be completed immediately, then the
+server sends a response in the form described for "transact", above.
+Otherwise, the server sends a JSON-RPC error response of the following
+form:
+
+ "result": null
+ "error": "canceled"
+ "id": the request "id" member
+
+The "cancel" notification itself has no reply.
+
+Notation for the Wire Protocol
+------------------------------
+
+<table>
+
+ An <id> that names a table.
+
+<column>
+
+ An <id> that names a table column.
+
+<row>
+
+ A JSON object that describes a table row or a subset of a table
+ row. Each member is the name of a table column paired with the
+ <value> of that column.
+
+<value>
+
+ A JSON value that represents the value of a column in a table row,
+ one of <atom>, a <set>, or a <map>.
+
+<atom>
+
+ A JSON value that represents a scalar value for a column, one of
+ <string>, <number>, <boolean>, <uuid>, <named-uuid>.
+
+<set>
+
+ A 2-element JSON array that represents a database set value. The
+ first element of the array must be the string "set" and the second
+ element must be an array of zero or more <atom>s giving the values
+ in the set. All of the <atom>s must have the same type.
+
+<map>
+
+ A 2-element JSON array that represents a database map value. The
+ first element of the array must be the string "map" and the second
+ element must be an array of zero or more <pair>s giving the values
+ in the map. All of the <pair>s must have the same key and value
+ types.
+
+ (JSON objects are not used to represent <map> because JSON only
+ allows string names in an object.)
+
+<pair>
+
+ A 2-element JSON array that represents a pair within a database
+ map. The first element is an <atom> that represents the key, the
+ second element is an <atom> that represents the value.
+
+<uuid>
+
+ A 2-element JSON array that represents a UUID. The first element
+ of the array must be the string "uuid" and the second element must
+ be a 36-character string giving the UUID in the format described
+ by RFC 4122. For example, the following <uuid> represents the
+ UUID 550e8400-e29b-41d4-a716-446655440000:
+
+ ["uuid", "550e8400-e29b-41d4-a716-446655440000"]
+
+<named-uuid>
+
+ A 2-element JSON array that represents the UUID of a row inserted
+ in a previous "insert" operation within the same transaction. The
+ first element of the array must be the string "named-uuid" and the
+ second element must be the string specified on a previous "insert"
+ operation's "uuid-name". For example, if a previous "insert"
+ operation specified a "uuid-name" of "myrow", the following
+ <named-uuid> represents the UUID created by that operation:
+
+ ["named-uuid", "myrow"]
+
+<condition>
+
+ A 3-element JSON array of the form [<column>, <function>,
+ <value>] that represents a test on 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
+
+ <function> must be "<", "<=", "==", "!=", ">=", ">",
+ "includes", or "excludes".
+
+ The test is true if the column's value satisfies the
+ relation <function> <value>, e.g. if the column has value
+ 1 and <value> is 2, the test is true if <function> is "<",
+ "<=" or "!=", but not otherwise.
+
+ "includes" is equivalent to "=="; "excludes" is equivalent
+ to "!=".
+
+ boolean
+ string
+ uuid
+
+ <function> must be "!=", "==", "includes", or "excludes".
+
+ If <function> is "==" or "includes", the test is true if
+ the column's value equals <value>. If <function> is "!="
+ or "excludes", the test is inverted.
+
+ set
+ map
+
+ <function> must be "!=", "==", "includes", or "excludes".
+
+ If <function> is "==", the test is true if the column's
+ value contains exactly the same values (for sets) or pairs
+ (for maps). If <function> is "!=", the test is inverted.
+
+ If <function> is "includes", the test is true if the
+ column's value contains all of the values (for sets) or
+ pairs (for maps) in <value>. The column's value may also
+ contain other values or pairs.
+
+ If <function> is "excludes", the test is true if the
+ column's value does not contain any of the values (for
+ sets) or pairs (for maps) in <value>. The column's value
+ may contain other values or pairs not in <value>.
+
+ If <function> is "includes" or "excludes", then 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 <function> is
+ "excludes", then the required type is additionally relaxed
+ in that <value> may have more than the maximum number of
+ elements specified by the column's type.
+
+<function>
+
+ One of "<", "<=", "==", "!=", ">=", ">", "includes", "excludes".
+
+Operations
+----------
+
+Each of the available operations is described below.
+
+insert
+......
+
+Request object members:
+
+ "op": "insert" required
+ "table": <table> required
+ "row": <row> required
+ "uuid-name": <string> optional
+
+Result object members:
+
+ "uuid": <uuid>
+
+Semantics:
+
+ Inserts "row" into "table". If "row" does not specify values
+ for all the columns in "table", those columns receive default
+ values.
+
+ The new row receives a new, randomly generated UUID, which is
+ returned as the "_uuid" member of the result. If "uuid-name"
+ is supplied, then the UUID is made available under that name
+ to later operations within the same transaction.
+
+select
+......
+
+Request object members:
+
+ "op": "select" required
+ "table": <table> required
+ "where": [<condition>*] required
+ "columns": [<column>*] optional
+
+Result object members:
+
+ "rows": [<row>*]
+
+Semantics:
+
+ Searches "table" for rows that match all the conditions specified
+ in "where". If "where" is an empty array, every row in "table" is
+ selected.
+
+ The "rows" member of the result is an array of objects. Each
+ object corresponds to a matching row, with each column
+ specified in "columns" as a member, the column's name as the
+ member name and its value as the member value. If "columns"
+ is not specified, all the table's columns are included. If
+ two rows of the result have the same values for all included
+ columns, only one copy of that row is included in "rows".
+ Specifying "_uuid" within "columns" will avoid dropping
+ duplicates, since every row has a unique UUID.
+
+ The ordering of rows within "rows" is unspecified.
+
+update
+......
+
+Request object members:
+
+ "op": "update" required
+ "table": <table> required
+ "where": [<condition>*] required
+ "row": <row> required
+
+Result object members:
+
+ "count": <integer>
+
+Semantics:
+
+ Updates rows in a table.
+
+ Searches "table" for rows that match all the conditions
+ specified in "where". For each matching row, changes the
+ value of each column specified in "row" to the value for that
+ column specified in "row".
+
+ The "_uuid" and "_version" columns of a table may not be updated.
+ 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.
+
+delete
+......
+
+Request object members:
+
+ "op": "delete" required
+ "table": <table> required
+ "where": [<condition>*] required
+
+Result object members:
+
+ "count": <integer>
+
+Semantics:
+
+ Deletes all the rows from "table" that match all the conditions
+ specified in "where".
+
+ The "count" member of the result specifies the number of deleted
+ rows.
+
+wait
+....
+
+Request object members:
+
+ "op": "wait" required
+ "timeout": <integer> optional
+ "table": <table> required
+ "where": [<condition>*] required
+ "columns": [<column>*] required
+ "until": "==" or "!=" required
+ "rows": [<row>*] required
+
+Result object members:
+
+ none
+
+Semantics:
+
+ Waits until a condition becomes true.
+
+ If "until" is "==", checks whether the query on "table" specified
+ by "where" and "columns", which is evaluated in the same way as
+ specified for "select", returns the result set specified by
+ "rows". If it does, then the operation completes successfully.
+ Otherwise, the entire transaction rolls back. It is automatically
+ restarted later, after a change in the database makes it possible
+ for the operation to succeed. The client will not receive a
+ response until the operation permanently succeeds or fails.
+
+ If "until" is "!=", the sense of the test is negated. That is, as
+ long as the query on "table" specified by "where" and "columns"
+ returns "rows", the transaction will be rolled back and restarted
+ later.
+
+ If "timeout" is specified, then the transaction aborts after the
+ specified number of milliseconds. The transaction is guaranteed
+ to be attempted at least once before it aborts. A "timeout" of 0
+ will abort the transaction on the first mismatch.
+
+Errors:
+
+ "error": "not supported"
+
+ One or more of the columns in this table do not support
+ triggers. This error will not occur if "timeout" is 0.
+
+ "error": "timed out"
+
+ The "timeout" was reached before the transaction was able to
+ complete.
+
+commit
+......
+
+Request object members:
+
+ "op": "commit" required
+ "durable": <boolean> required
+
+Result object members:
+
+ none
+
+Semantics:
+
+ If "durable" is specified as true, then the transaction, if it
+ commits, will be stored durably (to disk) before the reply is sent
+ to the client.
+
+Errors:
+
+ "error": "not supported"
+
+ When "durable" is true, this database implementation does not
+ support durable commits.
+
+abort
+.....
+
+Request object members:
+
+ "op": "abort" required
+
+Result object members:
+
+ (never succeeds)
+
+Semantics:
+
+ Aborts the transaction with an error. This may be useful for
+ testing.
+
+Errors:
+
+ "error": "aborted"
+
+ This operation always fails with this error.
diff --git a/ovsdb/automake.mk b/ovsdb/automake.mk
new file mode 100644
index 00000000..d2a3e04c
--- /dev/null
+++ b/ovsdb/automake.mk
@@ -0,0 +1,44 @@
+# libovsdb
+noinst_LIBRARIES += ovsdb/libovsdb.a
+ovsdb_libovsdb_a_SOURCES = \
+ ovsdb/column.c \
+ ovsdb/column.h \
+ ovsdb/condition.c \
+ ovsdb/condition.h \
+ ovsdb/execution.c \
+ ovsdb/file.c \
+ ovsdb/file.h \
+ ovsdb/jsonrpc-server.c \
+ ovsdb/jsonrpc-server.h \
+ ovsdb/ovsdb-server.c \
+ ovsdb/ovsdb.c \
+ ovsdb/ovsdb.h \
+ ovsdb/query.c \
+ ovsdb/query.h \
+ ovsdb/row.c \
+ ovsdb/row.h \
+ ovsdb/table.c \
+ ovsdb/table.h \
+ ovsdb/trigger.c \
+ ovsdb/trigger.h \
+ ovsdb/transaction.c \
+ ovsdb/transaction.h
+
+# ovsdb-tool
+bin_PROGRAMS += ovsdb/ovsdb-tool
+ovsdb_ovsdb_tool_SOURCES = ovsdb/ovsdb-tool.c
+ovsdb_ovsdb_tool_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a
+
+## ovsdb-tool.8
+#man_MANS += ovsdb/ovsdb-tool.8
+#DISTCLEANFILES += ovsdb/ovsdb-tool.8
+#EXTRA_DIST += ovsdb/ovsdb-tool.8.in
+
+# ovsdb-server
+sbin_PROGRAMS += ovsdb/ovsdb-server
+ovsdb_ovsdb_server_SOURCES = ovsdb/ovsdb-server.c
+ovsdb_ovsdb_server_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a $(FAULT_LIBS)
+## ovsdb-server.8
+#man_MANS += ovsdb/ovsdb-server.8
+#DISTCLEANFILES += ovsdb/ovsdb-server.8
+#EXTRA_DIST += ovsdb/ovsdb-server.8.in
diff --git a/ovsdb/column.c b/ovsdb/column.c
new file mode 100644
index 00000000..1e8a2d09
--- /dev/null
+++ b/ovsdb/column.c
@@ -0,0 +1,232 @@
+/* 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 "ovsdb/column.h"
+
+#include <stdlib.h>
+
+#include "column.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "table.h"
+#include "util.h"
+
+struct ovsdb_column *
+ovsdb_column_create(const char *name, const char *comment,
+ bool mutable, bool persistent,
+ const struct ovsdb_type *type)
+{
+ struct ovsdb_column *ts;
+
+ ts = xzalloc(sizeof *ts);
+ ts->name = xstrdup(name);
+ ts->comment = comment ? xstrdup(comment) : NULL;
+ ts->mutable = mutable;
+ ts->persistent = persistent;
+ ts->type = *type;
+
+ return ts;
+}
+
+void
+ovsdb_column_destroy(struct ovsdb_column *column)
+{
+ free(column->name);
+ free(column->comment);
+ free(column);
+}
+
+struct ovsdb_error *
+ovsdb_column_from_json(const struct json *json, const char *name,
+ struct ovsdb_column **columnp)
+{
+ const struct json *comment, *mutable, *ephemeral, *type_json;
+ struct ovsdb_error *error;
+ struct ovsdb_type type;
+ struct ovsdb_parser parser;
+ bool persistent;
+
+ *columnp = NULL;
+
+ ovsdb_parser_init(&parser, json, "schema for column %s", name);
+ comment = ovsdb_parser_member(&parser, "comment", OP_STRING | OP_OPTIONAL);
+ mutable = ovsdb_parser_member(&parser, "mutable",
+ OP_TRUE | OP_FALSE | OP_OPTIONAL);
+ ephemeral = ovsdb_parser_member(&parser, "ephemeral",
+ OP_TRUE | OP_FALSE | OP_OPTIONAL);
+ type_json = ovsdb_parser_member(&parser, "type", OP_STRING | OP_OBJECT);
+ error = ovsdb_parser_finish(&parser);
+ if (error) {
+ return error;
+ }
+
+ error = ovsdb_type_from_json(&type, type_json);
+ if (error) {
+ return error;
+ }
+
+ persistent = ephemeral ? !json_boolean(ephemeral) : true;
+ *columnp = ovsdb_column_create(name,
+ comment ? json_string(comment) : NULL,
+ mutable ? json_boolean(mutable) : true,
+ persistent, &type);
+ return NULL;
+}
+
+struct json *
+ovsdb_column_to_json(const struct ovsdb_column *column)
+{
+ struct json *json = json_object_create();
+ if (column->comment) {
+ json_object_put_string(json, "comment", column->comment);
+ }
+ if (!column->mutable) {
+ json_object_put(json, "mutable", json_boolean_create(false));
+ }
+ if (!column->persistent) {
+ json_object_put(json, "ephemeral", json_boolean_create(true));
+ }
+ json_object_put(json, "type", ovsdb_type_to_json(&column->type));
+ return json;
+}
+
+void
+ovsdb_column_set_init(struct ovsdb_column_set *set)
+{
+ set->columns = NULL;
+ set->n_columns = set->allocated_columns = 0;
+}
+
+void
+ovsdb_column_set_destroy(struct ovsdb_column_set *set)
+{
+ free(set->columns);
+}
+
+void
+ovsdb_column_set_clone(struct ovsdb_column_set *new,
+ const struct ovsdb_column_set *old)
+{
+ new->columns = xmemdup(old->columns,
+ old->n_columns * sizeof *old->columns);
+ new->n_columns = new->allocated_columns = old->n_columns;
+}
+
+struct ovsdb_error *
+ovsdb_column_set_from_json(const struct json *json,
+ const struct ovsdb_table *table,
+ struct ovsdb_column_set *set)
+{
+ ovsdb_column_set_init(set);
+ if (!json) {
+ struct shash_node *node;
+
+ SHASH_FOR_EACH (node, &table->schema->columns) {
+ const struct ovsdb_column *column = node->data;
+ ovsdb_column_set_add(set, column);
+ }
+
+ return NULL;
+ } else {
+ size_t i;
+
+ if (json->type != JSON_ARRAY) {
+ goto error;
+ }
+
+ /* XXX this is O(n**2) */
+ for (i = 0; i < json->u.array.n; i++) {
+ struct ovsdb_column *column;
+
+ if (json->u.array.elems[i]->type != JSON_STRING) {
+ goto error;
+ }
+
+ column = shash_find_data(&table->schema->columns,
+ json->u.array.elems[i]->u.string);
+ if (ovsdb_column_set_contains(set, column->index)) {
+ goto error;
+ }
+ ovsdb_column_set_add(set, column);
+ }
+
+ return NULL;
+ }
+
+error:
+ ovsdb_column_set_destroy(set);
+ return ovsdb_syntax_error(json, NULL,
+ "array of distinct column names expected");
+}
+
+void
+ovsdb_column_set_add(struct ovsdb_column_set *set,
+ const struct ovsdb_column *column)
+{
+ if (set->n_columns >= set->allocated_columns) {
+ set->columns = x2nrealloc(set->columns, &set->allocated_columns,
+ sizeof *set->columns);
+ }
+ set->columns[set->n_columns++] = column;
+}
+
+void
+ovsdb_column_set_add_all(struct ovsdb_column_set *set,
+ const struct ovsdb_table *table)
+{
+ struct shash_node *node;
+
+ SHASH_FOR_EACH (node, &table->schema->columns) {
+ const struct ovsdb_column *column = node->data;
+ ovsdb_column_set_add(set, column);
+ }
+}
+
+bool
+ovsdb_column_set_contains(const struct ovsdb_column_set *set,
+ unsigned int column_index)
+{
+ size_t i;
+
+ for (i = 0; i < set->n_columns; i++) {
+ if (set->columns[i]->index == column_index) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* This comparison is sensitive to ordering of columns within a set, but that's
+ * good: the only existing caller wants to make sure that hash values are
+ * comparable, which is only true if column ordering is the same. */
+bool
+ovsdb_column_set_equals(const struct ovsdb_column_set *a,
+ const struct ovsdb_column_set *b)
+{
+ size_t i;
+
+ if (a->n_columns != b->n_columns) {
+ return false;
+ }
+ for (i = 0; i < a->n_columns; i++) {
+ if (a->columns[i] != b->columns[i]) {
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/ovsdb/column.h b/ovsdb/column.h
new file mode 100644
index 00000000..59421510
--- /dev/null
+++ b/ovsdb/column.h
@@ -0,0 +1,84 @@
+/* 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_COLUMN_H
+#define OVSDB_COLUMN_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+#include "ovsdb-types.h"
+
+struct ovsdb_table;
+
+/* A column or a column schema (currently there is no distinction). */
+struct ovsdb_column {
+ unsigned int index;
+ char *name;
+
+ char *comment;
+ bool mutable;
+ bool persistent;
+ struct ovsdb_type type;
+};
+
+/* A few columns appear in every table with standardized column indexes.
+ * These macros define those columns' indexes.
+ *
+ * Don't change these values, because ovsdb_query() depends on OVSDB_COL_UUID
+ * having value 0. */
+enum {
+ OVSDB_COL_UUID = 0, /* UUID for the row. */
+ OVSDB_COL_VERSION = 1, /* Version number for the row. */
+ OVSDB_N_STD_COLUMNS
+};
+
+struct ovsdb_column *ovsdb_column_create(
+ const char *name, const char *comment, bool mutable, bool persistent,
+ const struct ovsdb_type *);
+void ovsdb_column_destroy(struct ovsdb_column *);
+
+struct ovsdb_error *ovsdb_column_from_json(const struct json *,
+ const char *name,
+ struct ovsdb_column **)
+ WARN_UNUSED_RESULT;
+struct json *ovsdb_column_to_json(const struct ovsdb_column *);
+
+/* An unordered set of distinct columns. */
+
+struct ovsdb_column_set {
+ const struct ovsdb_column **columns;
+ size_t n_columns, allocated_columns;
+};
+
+#define OVSDB_COLUMN_SET_INITIALIZER { NULL, 0, 0 }
+
+void ovsdb_column_set_init(struct ovsdb_column_set *);
+void ovsdb_column_set_destroy(struct ovsdb_column_set *);
+void ovsdb_column_set_clone(struct ovsdb_column_set *,
+ const struct ovsdb_column_set *);
+struct ovsdb_error *ovsdb_column_set_from_json(const struct json *,
+ const struct ovsdb_table *,
+ struct ovsdb_column_set *);
+
+void ovsdb_column_set_add(struct ovsdb_column_set *,
+ const struct ovsdb_column *);
+void ovsdb_column_set_add_all(struct ovsdb_column_set *,
+ const struct ovsdb_table *);
+bool ovsdb_column_set_contains(const struct ovsdb_column_set *,
+ unsigned int column_index);
+bool ovsdb_column_set_equals(const struct ovsdb_column_set *,
+ const struct ovsdb_column_set *);
+
+#endif /* column.h */
diff --git a/ovsdb/condition.c b/ovsdb/condition.c
new file mode 100644
index 00000000..0342b8e8
--- /dev/null
+++ b/ovsdb/condition.c
@@ -0,0 +1,284 @@
+/* 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 "condition.h"
+
+#include <limits.h>
+
+#include "column.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "row.h"
+#include "table.h"
+
+struct ovsdb_error *
+ovsdb_function_from_string(const char *name, enum ovsdb_function *function)
+{
+#define OVSDB_FUNCTION(ENUM, NAME) \
+ if (!strcmp(name, NAME)) { \
+ *function = ENUM; \
+ return NULL; \
+ }
+ OVSDB_FUNCTIONS;
+#undef OVSDB_FUNCTION
+
+ return ovsdb_syntax_error(NULL, "unknown function",
+ "No function named %s.", name);
+}
+
+const char *
+ovsdb_function_to_string(enum ovsdb_function function)
+{
+ switch (function) {
+#define OVSDB_FUNCTION(ENUM, NAME) case ENUM: return NAME;
+ OVSDB_FUNCTIONS;
+#undef OVSDB_FUNCTION
+ }
+
+ return NULL;
+}
+
+
+static WARN_UNUSED_RESULT struct ovsdb_error *
+ovsdb_clause_from_json(const struct ovsdb_table_schema *ts,
+ const struct json *json,
+ const struct ovsdb_symbol_table *symtab,
+ struct ovsdb_clause *clause)
+{
+ const struct json_array *array;
+ struct ovsdb_error *error;
+ const char *function_name;
+ const char *column_name;
+ struct ovsdb_type type;
+
+ 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 condition.");
+ }
+ array = json_array(json);
+
+ column_name = json_string(array->elems[0]);
+ clause->column = ovsdb_table_schema_get_column(ts, column_name);
+ if (!clause->column) {
+ return ovsdb_syntax_error(json, "unknown column",
+ "No column %s in table %s.",
+ column_name, ts->name);
+ }
+ type = clause->column->type;
+
+ function_name = json_string(array->elems[1]);
+ error = ovsdb_function_from_string(function_name, &clause->function);
+ if (error) {
+ return error;
+ }
+
+ /* Type-check and relax restrictions on 'type' if appropriate. */
+ switch (clause->function) {
+ case OVSDB_F_LT:
+ case OVSDB_F_LE:
+ case OVSDB_F_GT:
+ case OVSDB_F_GE:
+ /* XXX should we also allow these operators for types with n_min == 0,
+ * n_max == 1? (They would always be "false" if the value was
+ * missing.) */
+ if (!ovsdb_type_is_scalar(&type)
+ || (type.key_type != OVSDB_TYPE_INTEGER
+ && type.key_type != OVSDB_TYPE_REAL)) {
+ char *s = ovsdb_type_to_english(&type);
+ error = ovsdb_syntax_error(
+ json, NULL, "Type mismatch: \"%s\" operator may not be "
+ "applied to column %s of type %s.",
+ ovsdb_function_to_string(clause->function),
+ clause->column->name, s);
+ free(s);
+ return error;
+ }
+ break;
+
+ case OVSDB_F_EQ:
+ case OVSDB_F_NE:
+ break;
+
+ case OVSDB_F_EXCLUDES:
+ if (!ovsdb_type_is_scalar(&type)) {
+ type.n_min = 0;
+ type.n_max = UINT_MAX;
+ }
+ break;
+
+ case OVSDB_F_INCLUDES:
+ if (!ovsdb_type_is_scalar(&type)) {
+ type.n_min = 0;
+ }
+ break;
+ }
+ return ovsdb_datum_from_json(&clause->arg, &type, array->elems[2], symtab);
+}
+
+static void
+ovsdb_clause_free(struct ovsdb_clause *clause)
+{
+ ovsdb_datum_destroy(&clause->arg, &clause->column->type);
+}
+
+static int
+compare_clauses_3way(const void *a_, const void *b_)
+{
+ const struct ovsdb_clause *a = a_;
+ const struct ovsdb_clause *b = b_;
+
+ if (a->function != b->function) {
+ /* Bring functions to the front based on the fraction of table rows
+ * that they are (heuristically) expected to leave in the query
+ * results. Note that "enum ovsdb_function" is intentionally ordered
+ * to make this trivial. */
+ return a->function < b->function ? -1 : 1;
+ } else if (a->column->index != b->column->index) {
+ if (a->column->index < OVSDB_N_STD_COLUMNS
+ || b->column->index < OVSDB_N_STD_COLUMNS) {
+ /* Bring the standard columns and in particular the UUID column
+ * (since OVSDB_COL_UUID has value 0) to the front. We have an
+ * index on the UUID column, so that makes our queries cheaper. */
+ return a->column->index < b->column->index ? -1 : 1;
+ } else {
+ /* Order clauses predictably to make testing easier. */
+ return strcmp(a->column->name, b->column->name);
+ }
+ } else {
+ return 0;
+ }
+}
+
+struct ovsdb_error *
+ovsdb_condition_from_json(const struct ovsdb_table_schema *ts,
+ const struct json *json,
+ const struct ovsdb_symbol_table *symtab,
+ struct ovsdb_condition *cnd)
+{
+ const struct json_array *array = json_array(json);
+ size_t i;
+
+ cnd->clauses = xmalloc(array->n * sizeof *cnd->clauses);
+ cnd->n_clauses = 0;
+ for (i = 0; i < array->n; i++) {
+ struct ovsdb_error *error;
+ error = ovsdb_clause_from_json(ts, array->elems[i], symtab,
+ &cnd->clauses[i]);
+ if (error) {
+ ovsdb_condition_destroy(cnd);
+ cnd->clauses = NULL;
+ cnd->n_clauses = 0;
+ return error;
+ }
+ cnd->n_clauses++;
+ }
+
+ /* A real database would have a query optimizer here. */
+ qsort(cnd->clauses, cnd->n_clauses, sizeof *cnd->clauses,
+ compare_clauses_3way);
+
+ return NULL;
+}
+
+static struct json *
+ovsdb_clause_to_json(const struct ovsdb_clause *clause)
+{
+ return json_array_create_3(
+ json_string_create(clause->column->name),
+ json_string_create(ovsdb_function_to_string(clause->function)),
+ ovsdb_datum_to_json(&clause->arg, &clause->column->type));
+}
+
+struct json *
+ovsdb_condition_to_json(const struct ovsdb_condition *cnd)
+{
+ struct json **clauses;
+ size_t i;
+
+ clauses = xmalloc(cnd->n_clauses * sizeof *clauses);
+ for (i = 0; i < cnd->n_clauses; i++) {
+ clauses[i] = ovsdb_clause_to_json(&cnd->clauses[i]);
+ }
+ return json_array_create(clauses, cnd->n_clauses);
+}
+
+bool
+ovsdb_condition_evaluate(const struct ovsdb_row *row,
+ const struct ovsdb_condition *cnd)
+{
+ size_t i;
+
+ for (i = 0; i < cnd->n_clauses; i++) {
+ const struct ovsdb_clause *c = &cnd->clauses[i];
+ const struct ovsdb_datum *field = &row->fields[c->column->index];
+ const struct ovsdb_datum *arg = &cnd->clauses[i].arg;
+ const struct ovsdb_type *type = &c->column->type;
+
+ if (ovsdb_type_is_scalar(type)) {
+ int cmp = ovsdb_atom_compare_3way(&field->keys[0], &arg->keys[0],
+ type->key_type);
+ switch (c->function) {
+ case OVSDB_F_LT:
+ return cmp < 0;
+ case OVSDB_F_LE:
+ return cmp <= 0;
+ case OVSDB_F_EQ:
+ case OVSDB_F_INCLUDES:
+ return cmp == 0;
+ case OVSDB_F_NE:
+ case OVSDB_F_EXCLUDES:
+ return cmp != 0;
+ case OVSDB_F_GE:
+ return cmp >= 0;
+ case OVSDB_F_GT:
+ return cmp > 0;
+ }
+ } else {
+ switch (c->function) {
+ case OVSDB_F_EQ:
+ return ovsdb_datum_equals(field, arg, type);
+ case OVSDB_F_NE:
+ return !ovsdb_datum_equals(field, arg, type);
+ case OVSDB_F_INCLUDES:
+ return ovsdb_datum_includes_all(arg, field, type);
+ case OVSDB_F_EXCLUDES:
+ return ovsdb_datum_excludes_all(arg, field, type);
+ case OVSDB_F_LT:
+ case OVSDB_F_LE:
+ case OVSDB_F_GE:
+ case OVSDB_F_GT:
+ NOT_REACHED();
+ }
+ }
+ NOT_REACHED();
+ }
+
+ return true;
+}
+
+void
+ovsdb_condition_destroy(struct ovsdb_condition *cnd)
+{
+ size_t i;
+
+ for (i = 0; i < cnd->n_clauses; i++) {
+ ovsdb_clause_free(&cnd->clauses[i]);
+ }
+ free(cnd->clauses);
+}
diff --git a/ovsdb/condition.h b/ovsdb/condition.h
new file mode 100644
index 00000000..8c422b95
--- /dev/null
+++ b/ovsdb/condition.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_CONDITION_H
+#define OVSDB_CONDITION_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_FUNCTIONS \
+ OVSDB_FUNCTION(OVSDB_F_EQ, "==") \
+ OVSDB_FUNCTION(OVSDB_F_INCLUDES, "includes") \
+ OVSDB_FUNCTION(OVSDB_F_LE, "<=") \
+ OVSDB_FUNCTION(OVSDB_F_LT, "<") \
+ OVSDB_FUNCTION(OVSDB_F_GE, ">=") \
+ OVSDB_FUNCTION(OVSDB_F_GT, ">") \
+ OVSDB_FUNCTION(OVSDB_F_EXCLUDES, "excludes") \
+ OVSDB_FUNCTION(OVSDB_F_NE, "!=")
+
+enum ovsdb_function {
+#define OVSDB_FUNCTION(ENUM, NAME) ENUM,
+ OVSDB_FUNCTIONS
+#undef OVSDB_FUNCTION
+};
+
+struct ovsdb_error *ovsdb_function_from_string(const char *,
+ enum ovsdb_function *)
+ WARN_UNUSED_RESULT;
+const char *ovsdb_function_to_string(enum ovsdb_function);
+
+struct ovsdb_clause {
+ enum ovsdb_function function;
+ const struct ovsdb_column *column;
+ struct ovsdb_datum arg;
+};
+
+struct ovsdb_condition {
+ struct ovsdb_clause *clauses;
+ size_t n_clauses;
+};
+
+#define OVSDB_CONDITION_INITIALIZER { NULL, 0 }
+
+struct ovsdb_error *ovsdb_condition_from_json(
+ const struct ovsdb_table_schema *,
+ const struct json *, const struct ovsdb_symbol_table *,
+ struct ovsdb_condition *) WARN_UNUSED_RESULT;
+struct json *ovsdb_condition_to_json(const struct ovsdb_condition *);
+void ovsdb_condition_destroy(struct ovsdb_condition *);
+bool ovsdb_condition_evaluate(const struct ovsdb_row *,
+ const struct ovsdb_condition *);
+
+#endif /* ovsdb/condition.h */
diff --git a/ovsdb/execution.c b/ovsdb/execution.c
new file mode 100644
index 00000000..25b34b19
--- /dev/null
+++ b/ovsdb/execution.c
@@ -0,0 +1,613 @@
+/* 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 <assert.h>
+#include <limits.h>
+
+#include "column.h"
+#include "condition.h"
+#include "file.h"
+#include "json.h"
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "ovsdb.h"
+#include "query.h"
+#include "row.h"
+#include "table.h"
+#include "timeval.h"
+#include "transaction.h"
+
+struct ovsdb_execution {
+ struct ovsdb *db;
+ struct ovsdb_txn *txn;
+ struct ovsdb_symbol_table *symtab;
+ bool durable;
+
+ /* Triggers. */
+ long long int elapsed_msec;
+ long long int timeout_msec;
+};
+
+typedef struct ovsdb_error *ovsdb_operation_executor(struct ovsdb_execution *,
+ struct ovsdb_parser *,
+ struct json *result);
+
+static struct ovsdb_error *do_commit(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_delete;
+static ovsdb_operation_executor ovsdb_execute_wait;
+static ovsdb_operation_executor ovsdb_execute_commit;
+static ovsdb_operation_executor ovsdb_execute_abort;
+
+static ovsdb_operation_executor *
+lookup_executor(const char *name)
+{
+ struct ovsdb_operation {
+ const char *name;
+ ovsdb_operation_executor *executor;
+ };
+
+ static const struct ovsdb_operation operations[] = {
+ { "insert", ovsdb_execute_insert },
+ { "select", ovsdb_execute_select },
+ { "update", ovsdb_execute_update },
+ { "delete", ovsdb_execute_delete },
+ { "wait", ovsdb_execute_wait },
+ { "commit", ovsdb_execute_commit },
+ { "abort", ovsdb_execute_abort },
+ };
+
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(operations); i++) {
+ const struct ovsdb_operation *c = &operations[i];
+ if (!strcmp(c->name, name)) {
+ return c->executor;
+ }
+ }
+ return NULL;
+}
+
+struct json *
+ovsdb_execute(struct ovsdb *db, const struct json *params,
+ long long int elapsed_msec, long long int *timeout_msec)
+{
+ struct ovsdb_execution x;
+ struct ovsdb_error *error;
+ struct json *results;
+ size_t n_operations;
+ size_t i;
+
+ if (params->type != JSON_ARRAY) {
+ struct ovsdb_error *error;
+
+ error = ovsdb_syntax_error(params, NULL, "array expected");
+ results = ovsdb_error_to_json(error);
+ ovsdb_error_destroy(error);
+ return results;
+ }
+
+ x.db = db;
+ x.txn = ovsdb_txn_create(db);
+ x.symtab = ovsdb_symbol_table_create();
+ x.durable = false;
+ x.elapsed_msec = elapsed_msec;
+ x.timeout_msec = LLONG_MAX;
+ results = NULL;
+
+ results = json_array_create_empty();
+ n_operations = params->u.array.n;
+ error = NULL;
+ for (i = 0; i < n_operations; i++) {
+ struct json *operation = params->u.array.elems[i];
+ struct ovsdb_error *parse_error;
+ struct ovsdb_parser parser;
+ struct json *result;
+ const struct json *op;
+
+ /* Parse and execute operation. */
+ ovsdb_parser_init(&parser, operation,
+ "ovsdb operation %zu of %zu", i + 1, n_operations);
+ op = ovsdb_parser_member(&parser, "op", OP_ID);
+ result = json_object_create();
+ if (op) {
+ const char *op_name = json_string(op);
+ ovsdb_operation_executor *executor = lookup_executor(op_name);
+ if (executor) {
+ error = executor(&x, &parser, result);
+ } else {
+ error = ovsdb_syntax_error(operation, "unknown operation",
+ "No operation \"%s\"", op_name);
+ }
+ } else {
+ assert(ovsdb_parser_has_error(&parser));
+ }
+
+ /* A parse error overrides any other error.
+ * An error overrides any other result. */
+ parse_error = ovsdb_parser_finish(&parser);
+ if (parse_error) {
+ ovsdb_error_destroy(error);
+ error = parse_error;
+ }
+ if (error) {
+ json_destroy(result);
+ result = ovsdb_error_to_json(error);
+ }
+ if (error && !strcmp(ovsdb_error_get_tag(error), "not supported")
+ && timeout_msec) {
+ ovsdb_txn_abort(x.txn);
+ *timeout_msec = x.timeout_msec;
+ ovsdb_error_destroy(error);
+ json_destroy(results);
+ return NULL;
+ }
+
+ /* Add result to array. */
+ json_array_add(results, result);
+ if (error) {
+ break;
+ }
+ }
+
+ if (!error) {
+ /* Commit transaction. Bail if commit encounters error. */
+ error = do_commit(&x);
+ if (error) {
+ json_array_add(results, ovsdb_error_to_json(error));
+ }
+ } else {
+ ovsdb_txn_abort(x.txn);
+ }
+
+ while (json_array(results)->n < n_operations) {
+ json_array_add(results, json_null_create());
+ }
+
+ ovsdb_error_destroy(error);
+ ovsdb_symbol_table_destroy(x.symtab);
+
+ return results;
+}
+
+struct ovsdb_error *
+ovsdb_execute_commit(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+ struct json *result UNUSED)
+{
+ const struct json *durable;
+
+ durable = ovsdb_parser_member(parser, "durable", OP_BOOLEAN);
+ if (durable && json_boolean(durable)) {
+ x->durable = true;
+ }
+ return NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_execute_abort(struct ovsdb_execution *x UNUSED,
+ struct ovsdb_parser *parser UNUSED,
+ struct json *result UNUSED)
+{
+ return ovsdb_error("aborted", "aborted by request");
+}
+
+static struct ovsdb_error *
+do_commit(struct ovsdb_execution *x)
+{
+ if (x->db->file) {
+ struct ovsdb_error *error;
+ struct json *json;
+
+ json = ovsdb_txn_to_json(x->txn);
+ if (!json) {
+ /* Nothing to commit. */
+ return NULL;
+ }
+
+ error = ovsdb_file_write(x->db->file, json);
+ json_destroy(json);
+ if (error) {
+ return ovsdb_wrap_error(error, "writing transaction failed");
+ }
+
+ if (x->durable) {
+ error = ovsdb_file_commit(x->db->file);
+ if (error) {
+ return ovsdb_wrap_error(error,
+ "committing transaction failed");
+ }
+ }
+ }
+
+ ovsdb_txn_commit(x->txn);
+ return NULL;
+}
+
+static struct ovsdb_table *
+parse_table(struct ovsdb_execution *x,
+ struct ovsdb_parser *parser, const char *member)
+{
+ struct ovsdb_table *table;
+ const char *table_name;
+ const struct json *json;
+
+ json = ovsdb_parser_member(parser, member, OP_ID);
+ if (!json) {
+ return NULL;
+ }
+ table_name = json_string(json);
+
+ table = shash_find_data(&x->db->tables, table_name);
+ if (!table) {
+ ovsdb_parser_raise_error(parser, "No table named %s.", table_name);
+ }
+ return table;
+}
+
+static WARN_UNUSED_RESULT struct ovsdb_error *
+parse_row(struct ovsdb_parser *parser, const char *member,
+ const struct ovsdb_table *table,
+ const struct ovsdb_symbol_table *symtab,
+ struct ovsdb_row **rowp, struct ovsdb_column_set *columns)
+{
+ struct ovsdb_error *error;
+ const struct json *json;
+ struct ovsdb_row *row;
+
+ *rowp = NULL;
+
+ if (!table) {
+ return OVSDB_BUG("null table");
+ }
+ json = ovsdb_parser_member(parser, member, OP_OBJECT);
+ if (!json) {
+ return OVSDB_BUG("null row member");
+ }
+
+ row = ovsdb_row_create(table);
+ error = ovsdb_row_from_json(row, json, symtab, columns);
+ if (error) {
+ ovsdb_row_destroy(row);
+ return error;
+ } else {
+ *rowp = row;
+ return NULL;
+ }
+}
+
+struct ovsdb_error *
+ovsdb_execute_insert(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+ struct json *result)
+{
+ struct ovsdb_table *table;
+ struct ovsdb_row *row = NULL;
+ const struct json *uuid_name;
+ struct ovsdb_error *error;
+
+ table = parse_table(x, parser, "table");
+ uuid_name = ovsdb_parser_member(parser, "uuid-name", OP_ID | OP_OPTIONAL);
+ error = ovsdb_parser_get_error(parser);
+ if (!error) {
+ error = parse_row(parser, "row", table, x->symtab, &row, NULL);
+ }
+ if (!error) {
+ uuid_generate(ovsdb_row_get_uuid_rw(row));
+ if (uuid_name) {
+ ovsdb_symbol_table_put(x->symtab, json_string(uuid_name),
+ ovsdb_row_get_uuid(row));
+ }
+ ovsdb_txn_row_insert(x->txn, row);
+ json_object_put(result, "uuid",
+ ovsdb_datum_to_json(&row->fields[OVSDB_COL_UUID],
+ &ovsdb_type_uuid));
+ row = NULL;
+ }
+ return error;
+}
+
+struct ovsdb_error *
+ovsdb_execute_select(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+ struct json *result)
+{
+ struct ovsdb_table *table;
+ const struct json *where, *columns_json, *sort_json;
+ struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+ struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+ struct ovsdb_column_set sort = OVSDB_COLUMN_SET_INITIALIZER;
+ struct ovsdb_error *error;
+
+ table = parse_table(x, parser, "table");
+ where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+ columns_json = ovsdb_parser_member(parser, "columns",
+ OP_ARRAY | OP_OPTIONAL);
+ sort_json = ovsdb_parser_member(parser, "sort", OP_ARRAY | OP_OPTIONAL);
+
+ error = ovsdb_parser_get_error(parser);
+ if (!error) {
+ error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+ &condition);
+ }
+ if (!error) {
+ error = ovsdb_column_set_from_json(columns_json, table, &columns);
+ }
+ if (!error) {
+ error = ovsdb_column_set_from_json(sort_json, table, &sort);
+ }
+ if (!error) {
+ struct ovsdb_row_set rows = OVSDB_ROW_SET_INITIALIZER;
+
+ ovsdb_query_distinct(table, &condition, &columns, &rows);
+ ovsdb_row_set_sort(&rows, &sort);
+ json_object_put(result, "rows",
+ ovsdb_row_set_to_json(&rows, &columns));
+
+ ovsdb_row_set_destroy(&rows);
+ }
+
+ ovsdb_column_set_destroy(&columns);
+ ovsdb_column_set_destroy(&sort);
+ ovsdb_condition_destroy(&condition);
+
+ return error;
+}
+
+struct update_row_cbdata {
+ size_t n_matches;
+ struct ovsdb_txn *txn;
+ const struct ovsdb_row *row;
+ const struct ovsdb_column_set *columns;
+};
+
+static bool
+update_row_cb(const struct ovsdb_row *row, void *ur_)
+{
+ struct update_row_cbdata *ur = ur_;
+
+ ur->n_matches++;
+ if (!ovsdb_row_equal_columns(row, ur->row, ur->columns)) {
+ ovsdb_row_update_columns(ovsdb_txn_row_modify(ur->txn, row),
+ ur->row, ur->columns);
+ }
+
+ return true;
+}
+
+struct ovsdb_error *
+ovsdb_execute_update(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+ struct json *result)
+{
+ struct ovsdb_table *table;
+ const struct json *where;
+ struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+ struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+ struct ovsdb_row *row = NULL;
+ struct update_row_cbdata ur;
+ struct ovsdb_error *error;
+
+ table = parse_table(x, parser, "table");
+ where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+ error = ovsdb_parser_get_error(parser);
+ if (!error) {
+ error = parse_row(parser, "row", table, x->symtab, &row, &columns);
+ }
+ if (!error) {
+ error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+ &condition);
+ }
+ if (!error) {
+ ur.n_matches = 0;
+ ur.txn = x->txn;
+ ur.row = row;
+ ur.columns = &columns;
+ ovsdb_query(table, &condition, update_row_cb, &ur);
+ json_object_put(result, "count", json_integer_create(ur.n_matches));
+ }
+
+ ovsdb_row_destroy(row);
+ ovsdb_column_set_destroy(&columns);
+ ovsdb_condition_destroy(&condition);
+
+ return error;
+}
+
+struct delete_row_cbdata {
+ size_t n_matches;
+ const struct ovsdb_table *table;
+ struct ovsdb_txn *txn;
+};
+
+static bool
+delete_row_cb(const struct ovsdb_row *row, void *dr_)
+{
+ struct delete_row_cbdata *dr = dr_;
+
+ dr->n_matches++;
+ ovsdb_txn_row_delete(dr->txn, row);
+
+ return true;
+}
+
+struct ovsdb_error *
+ovsdb_execute_delete(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+ struct json *result)
+{
+ struct ovsdb_table *table;
+ const struct json *where;
+ struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+ struct ovsdb_error *error;
+
+ where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+ table = parse_table(x, parser, "table");
+ error = ovsdb_parser_get_error(parser);
+ if (!error) {
+ error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+ &condition);
+ }
+ if (!error) {
+ struct delete_row_cbdata dr;
+
+ dr.n_matches = 0;
+ dr.table = table;
+ dr.txn = x->txn;
+ ovsdb_query(table, &condition, delete_row_cb, &dr);
+
+ json_object_put(result, "count", json_integer_create(dr.n_matches));
+ }
+
+ ovsdb_condition_destroy(&condition);
+
+ return error;
+}
+
+struct wait_auxdata {
+ struct ovsdb_row_hash *actual;
+ struct ovsdb_row_hash *expected;
+ bool *equal;
+};
+
+static bool
+ovsdb_execute_wait_query_cb(const struct ovsdb_row *row, void *aux_)
+{
+ struct wait_auxdata *aux = aux_;
+
+ if (ovsdb_row_hash_contains(aux->expected, row)) {
+ ovsdb_row_hash_insert(aux->actual, row);
+ return true;
+ } else {
+ /* The query row isn't in the expected result set, so the actual and
+ * expected results sets definitely differ and we can short-circuit the
+ * rest of the query. */
+ *aux->equal = false;
+ return false;
+ }
+}
+
+static struct ovsdb_error *
+ovsdb_execute_wait(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+ struct json *result UNUSED)
+{
+ struct ovsdb_table *table;
+ const struct json *timeout, *where, *columns_json, *until, *rows;
+ struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+ struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+ struct ovsdb_row_hash expected = OVSDB_ROW_HASH_INITIALIZER(expected);
+ struct ovsdb_row_hash actual = OVSDB_ROW_HASH_INITIALIZER(actual);
+ struct ovsdb_error *error;
+ struct wait_auxdata aux;
+ long long int timeout_msec = 0;
+ size_t i;
+
+ timeout = ovsdb_parser_member(parser, "timeout", OP_NUMBER | OP_OPTIONAL);
+ where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+ columns_json = ovsdb_parser_member(parser, "columns",
+ OP_ARRAY | OP_OPTIONAL);
+ until = ovsdb_parser_member(parser, "until", OP_STRING);
+ rows = ovsdb_parser_member(parser, "rows", OP_ARRAY);
+ table = parse_table(x, parser, "table");
+ error = ovsdb_parser_get_error(parser);
+ if (!error) {
+ error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+ &condition);
+ }
+ if (!error) {
+ error = ovsdb_column_set_from_json(columns_json, table, &columns);
+ }
+ if (!error) {
+ if (timeout) {
+ timeout_msec = MIN(LLONG_MAX, json_real(timeout));
+ if (timeout_msec < 0) {
+ error = ovsdb_syntax_error(timeout, NULL,
+ "timeout must be nonnegative");
+ } else if (timeout_msec < x->timeout_msec) {
+ x->timeout_msec = timeout_msec;
+ }
+ } else {
+ timeout_msec = LLONG_MAX;
+ }
+ if (strcmp(json_string(until), "==")
+ && strcmp(json_string(until), "!=")) {
+ error = ovsdb_syntax_error(until, NULL,
+ "\"until\" must be \"==\" or \"!=\"");
+ }
+ }
+ if (!error) {
+ /* Parse "rows" into 'expected'. */
+ ovsdb_row_hash_init(&expected, &columns);
+ for (i = 0; i < rows->u.array.n; i++) {
+ struct ovsdb_error *error;
+ struct ovsdb_row *row;
+
+ row = ovsdb_row_create(table);
+ error = ovsdb_row_from_json(row, rows->u.array.elems[i], x->symtab,
+ NULL);
+ if (error) {
+ break;
+ }
+
+ if (!ovsdb_row_hash_insert(&expected, row)) {
+ /* XXX Perhaps we should abort with an error or log a
+ * warning. */
+ ovsdb_row_destroy(row);
+ }
+ }
+ }
+ if (!error) {
+ /* Execute query. */
+ bool equal = true;
+ ovsdb_row_hash_init(&actual, &columns);
+ aux.actual = &actual;
+ aux.expected = &expected;
+ aux.equal = &equal;
+ ovsdb_query(table, &condition, ovsdb_execute_wait_query_cb, &aux);
+ if (equal) {
+ /* We know that every row in 'actual' is also in 'expected'. We
+ * also know that all of the rows in 'actual' are distinct and that
+ * all of the rows in 'expected' are distinct. Therefore, if
+ * 'actual' and 'expected' have the same number of rows, then they
+ * have the same content. */
+ size_t n_actual = ovsdb_row_hash_count(&actual);
+ size_t n_expected = ovsdb_row_hash_count(&expected);
+ equal = n_actual == n_expected;
+ }
+ if (!strcmp(json_string(until), "==") != equal) {
+ if (timeout && x->elapsed_msec >= timeout_msec) {
+ if (x->elapsed_msec) {
+ error = ovsdb_error("timed out",
+ "\"wait\" timed out after %lld ms",
+ x->elapsed_msec);
+ } else {
+ error = ovsdb_error("timed out", "\"wait\" timed out");
+ }
+ } else {
+ /* ovsdb_execute() will change this, if triggers really are
+ * supported. */
+ error = ovsdb_error("not supported", "triggers not supported");
+ }
+ }
+ }
+
+
+ ovsdb_row_hash_destroy(&expected, true);
+ ovsdb_row_hash_destroy(&actual, false);
+ ovsdb_column_set_destroy(&columns);
+ ovsdb_condition_destroy(&condition);
+
+ return error;
+}
diff --git a/ovsdb/file.c b/ovsdb/file.c
new file mode 100644
index 00000000..4883d76f
--- /dev/null
+++ b/ovsdb/file.c
@@ -0,0 +1,360 @@
+/* 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 "file.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "json.h"
+#include "lockfile.h"
+#include "ovsdb-error.h"
+#include "sha1.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_ovsdb_file
+#include "vlog.h"
+
+enum ovsdb_file_mode {
+ OVSDB_FILE_READ,
+ OVSDB_FILE_WRITE
+};
+
+struct ovsdb_file {
+ off_t offset;
+ char *name;
+ struct lockfile *lockfile;
+ FILE *stream;
+ struct ovsdb_error *read_error;
+ struct ovsdb_error *write_error;
+ enum ovsdb_file_mode mode;
+};
+
+struct ovsdb_error *
+ovsdb_file_open(const char *name, int flags, struct ovsdb_file **filep)
+{
+ struct lockfile *lockfile;
+ struct ovsdb_error *error;
+ struct ovsdb_file *file;
+ struct stat s;
+ FILE *stream;
+ int accmode;
+ int fd;
+
+ *filep = NULL;
+
+ accmode = flags & O_ACCMODE;
+ if (accmode == O_RDWR || accmode == O_WRONLY) {
+ int retval = lockfile_lock(name, 0, &lockfile);
+ if (retval) {
+ error = ovsdb_io_error(retval, "%s: failed to lock lockfile",
+ name);
+ goto error;
+ }
+ } else {
+ lockfile = NULL;
+ }
+
+ fd = open(name, flags, 0666);
+ if (fd < 0) {
+ const char *op = flags & O_CREAT && flags & O_EXCL ? "create" : "open";
+ error = ovsdb_io_error(errno, "%s: %s failed", op, name);
+ goto error_unlock;
+ }
+
+ if (!fstat(fd, &s) && s.st_size == 0) {
+ /* It's (probably) a new file so fsync() its parent directory to ensure
+ * that its directory entry is committed to disk. */
+ char *dir = dir_name(name);
+ int dirfd = open(dir, O_RDONLY);
+ if (dirfd >= 0) {
+ if (fsync(dirfd) && errno != EINVAL) {
+ VLOG_ERR("%s: fsync failed (%s)", dir, strerror(errno));
+ }
+ close(dirfd);
+ } else {
+ VLOG_ERR("%s: open failed (%s)", dir, strerror(errno));
+ }
+ free(dir);
+ }
+
+ stream = fdopen(fd, (accmode == O_RDONLY ? "rb"
+ : accmode == O_WRONLY ? "wb"
+ : "w+b"));
+ if (!stream) {
+ error = ovsdb_io_error(errno, "%s: fdopen failed", name);
+ goto error_close;
+ }
+
+ file = xmalloc(sizeof *file);
+ file->name = xstrdup(name);
+ file->lockfile = lockfile;
+ file->stream = stream;
+ file->offset = 0;
+ file->read_error = NULL;
+ file->write_error = NULL;
+ file->mode = OVSDB_FILE_READ;
+ *filep = file;
+ return NULL;
+
+error_close:
+ close(fd);
+error_unlock:
+ lockfile_unlock(lockfile);
+error:
+ return error;
+}
+
+void
+ovsdb_file_close(struct ovsdb_file *file)
+{
+ if (file) {
+ free(file->name);
+ fclose(file->stream);
+ lockfile_unlock(file->lockfile);
+ ovsdb_error_destroy(file->read_error);
+ ovsdb_error_destroy(file->write_error);
+ free(file);
+ }
+}
+
+static const char magic[] = "OVSDB JSON ";
+
+static bool
+parse_header(char *header, unsigned long int *length,
+ uint8_t sha1[SHA1_DIGEST_SIZE])
+{
+ char *p;
+
+ /* 'header' must consist of a magic string... */
+ if (strncmp(header, magic, strlen(magic))) {
+ return false;
+ }
+
+ /* ...followed by a length in bytes... */
+ *length = strtoul(header + strlen(magic), &p, 10);
+ if (!*length || *length == ULONG_MAX || *p != ' ') {
+ return false;
+ }
+ p++;
+
+ /* ...followed by a SHA-1 hash... */
+ if (!sha1_from_hex(sha1, p)) {
+ return false;
+ }
+ p += SHA1_HEX_DIGEST_LEN;
+
+ /* ...and ended by a new-line. */
+ if (*p != '\n') {
+ return false;
+ }
+
+ return true;
+}
+
+struct ovsdb_file_read_cbdata {
+ char input[4096];
+ struct ovsdb_file *file;
+ int error;
+ unsigned long length;
+};
+
+static struct ovsdb_error *
+parse_body(struct ovsdb_file *file, off_t offset, unsigned long int length,
+ uint8_t sha1[SHA1_DIGEST_SIZE], struct json **jsonp)
+{
+ unsigned long int bytes_left;
+ struct json_parser *parser;
+ struct sha1_ctx ctx;
+
+ sha1_init(&ctx);
+ parser = json_parser_create(JSPF_TRAILER);
+
+ bytes_left = length;
+ while (length > 0) {
+ char input[BUFSIZ];
+ int chunk;
+
+ chunk = MIN(length, sizeof input);
+ if (fread(input, 1, chunk, file->stream) != chunk) {
+ json_parser_abort(parser);
+ return ovsdb_io_error(ferror(file->stream) ? errno : EOF,
+ "%s: error reading %lu bytes "
+ "starting at offset %lld", file->name,
+ length, (long long int) offset);
+ }
+ sha1_update(&ctx, input, chunk);
+ json_parser_feed(parser, input, chunk);
+ length -= chunk;
+ }
+
+ sha1_final(&ctx, sha1);
+ *jsonp = json_parser_finish(parser);
+ return NULL;
+}
+
+struct ovsdb_error *
+ovsdb_file_read(struct ovsdb_file *file, struct json **jsonp)
+{
+ uint8_t expected_sha1[SHA1_DIGEST_SIZE];
+ uint8_t actual_sha1[SHA1_DIGEST_SIZE];
+ struct ovsdb_error *error;
+ off_t data_offset;
+ unsigned long data_length;
+ struct json *json;
+ char header[128];
+
+ *jsonp = json = NULL;
+
+ if (file->read_error) {
+ return ovsdb_error_clone(file->read_error);
+ } else if (file->mode == OVSDB_FILE_WRITE) {
+ return OVSDB_BUG("reading file in write mode");
+ }
+
+ if (!fgets(header, sizeof header, file->stream)) {
+ if (feof(file->stream)) {
+ error = NULL;
+ } else {
+ error = ovsdb_io_error(errno, "%s: read failed", file->name);
+ }
+ goto error;
+ }
+
+ if (!parse_header(header, &data_length, expected_sha1)) {
+ error = ovsdb_syntax_error(NULL, NULL, "%s: parse error at offset "
+ "%lld in header line \"%.*s\"",
+ file->name, (long long int) file->offset,
+ (int) strcspn(header, "\n"), header);
+ goto error;
+ }
+
+ data_offset = file->offset + strlen(header);
+ error = parse_body(file, data_offset, data_length, actual_sha1, &json);
+ if (error) {
+ goto error;
+ }
+
+ if (memcmp(expected_sha1, actual_sha1, SHA1_DIGEST_SIZE)) {
+ error = ovsdb_syntax_error(NULL, NULL, "%s: %lu bytes starting at "
+ "offset %lld have SHA-1 hash "SHA1_FMT" "
+ "but should have hash "SHA1_FMT,
+ file->name, data_length,
+ (long long int) data_offset,
+ SHA1_ARGS(actual_sha1),
+ SHA1_ARGS(expected_sha1));
+ goto error;
+ }
+
+ if (json->type == JSON_STRING) {
+ error = ovsdb_syntax_error(NULL, NULL, "%s: %lu bytes starting at "
+ "offset %lld are not valid JSON (%s)",
+ file->name, data_length,
+ (long long int) data_offset,
+ json->u.string);
+ goto error;
+ }
+
+ file->offset = data_offset + data_length;
+ *jsonp = json;
+ return 0;
+
+error:
+ file->read_error = ovsdb_error_clone(error);
+ json_destroy(json);
+ return error;
+}
+
+struct ovsdb_error *
+ovsdb_file_write(struct ovsdb_file *file, struct json *json)
+{
+ uint8_t sha1[SHA1_DIGEST_SIZE];
+ struct ovsdb_error *error;
+ char *json_string;
+ char header[128];
+ size_t length;
+
+ json_string = NULL;
+
+ if (file->write_error) {
+ return ovsdb_error_clone(file->write_error);
+ } else if (file->mode == OVSDB_FILE_READ) {
+ file->mode = OVSDB_FILE_WRITE;
+ if (fseeko(file->stream, file->offset, SEEK_SET)) {
+ error = ovsdb_io_error(errno, "%s: cannot seek to offset %lld",
+ file->name, (long long int) file->offset);
+ goto error;
+ }
+ if (ftruncate(fileno(file->stream), file->offset)) {
+ error = ovsdb_io_error(errno, "%s: cannot truncate to length %lld",
+ file->name, (long long int) file->offset);
+ goto error;
+ }
+ }
+
+ if (json->type != JSON_OBJECT && json->type != JSON_ARRAY) {
+ error = OVSDB_BUG("bad JSON type");
+ goto error;
+ }
+
+ /* Compose content. Add a new-line (replacing the null terminator) to make
+ * the file easier to read, even though it has no semantic value. */
+ json_string = json_to_string(json, 0);
+ length = strlen(json_string) + 1;
+ json_string[length - 1] = '\n';
+
+ /* Compose header. */
+ sha1_bytes(json_string, length, sha1);
+ snprintf(header, sizeof header, "%s%zu "SHA1_FMT"\n",
+ magic, length, SHA1_ARGS(sha1));
+
+ /* Write. */
+ if (fwrite(header, strlen(header), 1, file->stream) != 1
+ || fwrite(json_string, length, 1, file->stream) != 1
+ || fflush(file->stream))
+ {
+ error = ovsdb_io_error(errno, "%s: write failed", file->name);
+
+ /* Remove any partially written data, ignoring errors since there is
+ * nothing further we can do. */
+ ftruncate(fileno(file->stream), file->offset);
+
+ goto error;
+ }
+
+ file->offset += strlen(header) + length;
+ free(json_string);
+ return 0;
+
+error:
+ file->write_error = ovsdb_error_clone(error);
+ free(json_string);
+ return error;
+}
+
+struct ovsdb_error *
+ovsdb_file_commit(struct ovsdb_file *file)
+{
+ if (fsync(fileno(file->stream))) {
+ return ovsdb_io_error(errno, "%s: fsync failed", file->name);
+ }
+ return 0;
+}
diff --git a/ovsdb/file.h b/ovsdb/file.h
new file mode 100644
index 00000000..5178140a
--- /dev/null
+++ b/ovsdb/file.h
@@ -0,0 +1,36 @@
+/* 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_FILE_H
+#define OVSDB_FILE_H 1
+
+#include <sys/types.h>
+#include "compiler.h"
+
+struct json;
+struct ovsdb_file;
+
+struct ovsdb_error *ovsdb_file_open(const char *name, int flags,
+ struct ovsdb_file **) WARN_UNUSED_RESULT;
+void ovsdb_file_close(struct ovsdb_file *);
+
+struct ovsdb_error *ovsdb_file_read(struct ovsdb_file *, struct json **)
+ WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_file_write(struct ovsdb_file *, struct json *)
+ WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_file_commit(struct ovsdb_file *)
+ WARN_UNUSED_RESULT;
+
+#endif /* ovsdb/file.h */
diff --git a/ovsdb/jsonrpc-server.c b/ovsdb/jsonrpc-server.c
new file mode 100644
index 00000000..36c9a7a2
--- /dev/null
+++ b/ovsdb/jsonrpc-server.c
@@ -0,0 +1,362 @@
+/* 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 "jsonrpc-server.h"
+
+#include <errno.h>
+
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb.h"
+#include "stream.h"
+#include "svec.h"
+#include "timeval.h"
+#include "trigger.h"
+
+#define THIS_MODULE VLM_ovsdb_jsonrpc_server
+#include "vlog.h"
+
+struct ovsdb_jsonrpc_trigger {
+ struct ovsdb_trigger trigger;
+ struct ovsdb_jsonrpc_session *session;
+ struct hmap_node hmap_node; /* Element in session's trigger table. */
+ struct json *id;
+};
+
+static struct ovsdb_jsonrpc_trigger *ovsdb_jsonrpc_trigger_find(
+ struct ovsdb_jsonrpc_session *, const struct json *id, size_t hash);
+static void ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *);
+
+struct ovsdb_jsonrpc_session {
+ struct ovsdb_jsonrpc_server *server;
+ struct list node; /* Element in server's sessions list. */
+ struct jsonrpc *rpc;
+ struct hmap triggers;
+ struct list completions; /* Completed triggers. */
+};
+
+static void ovsdb_jsonrpc_session_open(struct ovsdb_jsonrpc_server *,
+ struct stream *);
+static void ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *);
+static void ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *,
+ struct jsonrpc_msg *);
+static void ovsdb_jsonrpc_session_got_notify(struct ovsdb_jsonrpc_session *,
+ struct jsonrpc_msg *);
+
+struct ovsdb_jsonrpc_server {
+ struct ovsdb *db;
+
+ struct list sessions; /* List of "struct ovsdb_jsonrpc_session"s. */
+ unsigned int n_sessions, max_sessions;
+ unsigned int max_triggers;
+
+ struct pstream **listeners;
+ size_t n_listeners, allocated_listeners;
+};
+
+static void ovsdb_jsonrpc_server_listen(struct ovsdb_jsonrpc_server *,
+ struct pstream *);
+
+int
+ovsdb_jsonrpc_server_create(struct ovsdb *db, const struct svec *active,
+ const struct svec *passive,
+ struct ovsdb_jsonrpc_server **serverp)
+{
+ struct ovsdb_jsonrpc_server *server;
+ const char *name;
+ int retval = 0;
+ size_t i;
+
+ server = xzalloc(sizeof *server);
+ server->db = db;
+ server->max_sessions = 64;
+ server->max_triggers = 64;
+ list_init(&server->sessions);
+
+ SVEC_FOR_EACH (i, name, active) {
+ struct stream *stream;
+ int error;
+
+ error = stream_open(name, &stream);
+ if (!error) {
+ ovsdb_jsonrpc_session_open(server, stream);
+ } else {
+ ovs_error(error, "%s: connection failed", name);
+ retval = error;
+ }
+ }
+
+ SVEC_FOR_EACH (i, name, passive) {
+ struct pstream *pstream;
+ int error;
+
+ error = pstream_open(name, &pstream);
+ if (!error) {
+ ovsdb_jsonrpc_server_listen(server, pstream);
+ } else {
+ ovs_error(error, "failed to listen on %s", name);
+ retval = error;
+ }
+ }
+
+ *serverp = server;
+ return retval;
+}
+
+void
+ovsdb_jsonrpc_server_run(struct ovsdb_jsonrpc_server *svr)
+{
+ struct ovsdb_jsonrpc_session *s, *next;
+ size_t i;
+
+ /* Accept new connections. */
+ for (i = 0; i < svr->n_listeners && svr->n_sessions < svr->max_sessions;) {
+ struct pstream *listener = svr->listeners[i];
+ struct stream *stream;
+ int error;
+
+ error = pstream_accept(listener, &stream);
+ if (!error) {
+ ovsdb_jsonrpc_session_open(svr, stream);
+ } else if (error == EAGAIN) {
+ i++;
+ } else if (error) {
+ VLOG_WARN("%s: accept failed: %s",
+ pstream_get_name(listener), strerror(error));
+ pstream_close(listener);
+ svr->listeners[i] = svr->listeners[--svr->n_listeners];
+ }
+ }
+
+ /* Handle each session. */
+ LIST_FOR_EACH_SAFE (s, next, struct ovsdb_jsonrpc_session, node,
+ &svr->sessions) {
+ struct jsonrpc_msg *msg;
+ int error;
+
+ jsonrpc_run(s->rpc);
+
+ while (!list_is_empty(&s->completions)) {
+ struct ovsdb_jsonrpc_trigger *t
+ = CONTAINER_OF(s->completions.next,
+ struct ovsdb_jsonrpc_trigger, trigger.node);
+ ovsdb_jsonrpc_trigger_complete(t);
+ }
+
+ if (!jsonrpc_get_backlog(s->rpc) && !jsonrpc_recv(s->rpc, &msg)) {
+ if (msg->type == JSONRPC_REQUEST) {
+ ovsdb_jsonrpc_session_got_request(s, msg);
+ } else if (msg->type == JSONRPC_NOTIFY) {
+ ovsdb_jsonrpc_session_got_notify(s, msg);
+ } else {
+ VLOG_WARN("%s: received unexpected %s message",
+ jsonrpc_get_name(s->rpc),
+ jsonrpc_msg_type_to_string(msg->type));
+ jsonrpc_error(s->rpc, EPROTO);
+ jsonrpc_msg_destroy(msg);
+ }
+ }
+
+ error = jsonrpc_get_status(s->rpc);
+ if (error) {
+ ovsdb_jsonrpc_session_close(s);
+ }
+ }
+}
+
+void
+ovsdb_jsonrpc_server_wait(struct ovsdb_jsonrpc_server *svr)
+{
+ struct ovsdb_jsonrpc_session *s;
+
+ if (svr->n_sessions < svr->max_sessions) {
+ size_t i;
+
+ for (i = 0; i < svr->n_sessions; i++) {
+ pstream_wait(svr->listeners[i]);
+ }
+ }
+
+ LIST_FOR_EACH (s, struct ovsdb_jsonrpc_session, node, &svr->sessions) {
+ jsonrpc_wait(s->rpc);
+ if (!jsonrpc_get_backlog(s->rpc)) {
+ jsonrpc_recv_wait(s->rpc);
+ }
+ }
+}
+
+static void
+ovsdb_jsonrpc_server_listen(struct ovsdb_jsonrpc_server *svr,
+ struct pstream *pstream)
+{
+ if (svr->n_listeners >= svr->allocated_listeners) {
+ svr->listeners = x2nrealloc(svr->listeners, &svr->allocated_listeners,
+ sizeof *svr->listeners);
+ }
+ svr->listeners[svr->n_listeners++] = pstream;
+}
+
+static struct ovsdb_jsonrpc_trigger *
+ovsdb_jsonrpc_trigger_find(struct ovsdb_jsonrpc_session *s,
+ const struct json *id, size_t hash)
+{
+ struct ovsdb_jsonrpc_trigger *t;
+
+ HMAP_FOR_EACH_WITH_HASH (t, struct ovsdb_jsonrpc_trigger, hmap_node, hash,
+ &s->triggers) {
+ if (json_equal(t->id, id)) {
+ return t;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *t)
+{
+ struct ovsdb_jsonrpc_session *s = t->session;
+
+ if (!jsonrpc_get_status(s->rpc)) {
+ struct jsonrpc_msg *reply;
+ struct json *result;
+
+ result = ovsdb_trigger_steal_result(&t->trigger);
+ if (result) {
+ reply = jsonrpc_create_reply(result, t->id);
+ } else {
+ reply = jsonrpc_create_error(json_string_create("canceled"),
+ t->id);
+ }
+ jsonrpc_send(s->rpc, reply);
+ }
+
+ json_destroy(t->id);
+ ovsdb_trigger_destroy(&t->trigger);
+ hmap_remove(&s->triggers, &t->hmap_node);
+ free(t);
+}
+
+static void
+ovsdb_jsonrpc_session_open(struct ovsdb_jsonrpc_server *svr,
+ struct stream *stream)
+{
+ struct ovsdb_jsonrpc_session *s;
+
+ s = xzalloc(sizeof *s);
+ s->server = svr;
+ list_push_back(&svr->sessions, &s->node);
+ s->rpc = jsonrpc_open(stream);
+ hmap_init(&s->triggers);
+ list_init(&s->completions);
+}
+
+static void
+ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *s)
+{
+ struct ovsdb_jsonrpc_trigger *t, *next;
+
+ jsonrpc_error(s->rpc, EOF);
+ HMAP_FOR_EACH_SAFE (t, next, struct ovsdb_jsonrpc_trigger, hmap_node,
+ &s->triggers) {
+ ovsdb_jsonrpc_trigger_complete(t);
+ }
+
+ jsonrpc_close(s->rpc);
+
+ list_remove(&s->node);
+ s->server->n_sessions--;
+}
+
+static struct jsonrpc_msg *
+execute_transaction(struct ovsdb_jsonrpc_session *s,
+ struct jsonrpc_msg *request)
+{
+ struct ovsdb_jsonrpc_trigger *t;
+ size_t hash;
+
+ /* Check for duplicate ID. */
+ hash = json_hash(request->id, 0);
+ t = ovsdb_jsonrpc_trigger_find(s, request->id, hash);
+ if (t) {
+ return jsonrpc_create_error(
+ json_string_create("duplicate request ID"), request->id);
+ }
+
+ /* Insert into trigger table. */
+ t = xmalloc(sizeof *t);
+ ovsdb_trigger_init(s->server->db,
+ &t->trigger, request->params, &s->completions,
+ time_msec());
+ t->session = s;
+ t->id = request->id;
+ hmap_insert(&s->triggers, &t->hmap_node, hash);
+
+ request->id = NULL;
+ request->params = NULL;
+
+ /* Complete early if possible. */
+ if (ovsdb_trigger_is_complete(&t->trigger)) {
+ ovsdb_jsonrpc_trigger_complete(t);
+ }
+
+ return NULL;
+}
+
+static void
+ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *s,
+ struct jsonrpc_msg *request)
+{
+ struct jsonrpc_msg *reply;
+
+ if (!strcmp(request->method, "transact")) {
+ reply = execute_transaction(s, request);
+ } else if (!strcmp(request->method, "get_schema")) {
+ reply = jsonrpc_create_reply(
+ ovsdb_schema_to_json(s->server->db->schema), request->id);
+ } else {
+ reply = jsonrpc_create_error(json_string_create("unknown method"),
+ request->id);
+ }
+
+ if (reply) {
+ jsonrpc_msg_destroy(request);
+ jsonrpc_send(s->rpc, reply);
+ }
+}
+
+static void
+execute_cancel(struct ovsdb_jsonrpc_session *s, struct jsonrpc_msg *request)
+{
+ size_t hash = json_hash(request->id, 0);
+ struct ovsdb_jsonrpc_trigger *t;
+
+ t = ovsdb_jsonrpc_trigger_find(s, request->params, hash);
+ if (t) {
+ ovsdb_jsonrpc_trigger_complete(t);
+ }
+}
+
+static void
+ovsdb_jsonrpc_session_got_notify(struct ovsdb_jsonrpc_session *s,
+ struct jsonrpc_msg *request)
+{
+ if (!strcmp(request->method, "cancel")) {
+ execute_cancel(s, request);
+ }
+ jsonrpc_msg_destroy(request);
+}
diff --git a/ovsdb/jsonrpc-server.h b/ovsdb/jsonrpc-server.h
new file mode 100644
index 00000000..49b5f8a9
--- /dev/null
+++ b/ovsdb/jsonrpc-server.h
@@ -0,0 +1,29 @@
+/* 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_JSONRPC_SERVER_H
+#define OVSDB_JSONRPC_SERVER_H 1
+
+struct ovsdb;
+struct ovsdb_jsonrpc_server;
+struct svec;
+
+int ovsdb_jsonrpc_server_create(struct ovsdb *, const struct svec *active,
+ const struct svec *passive,
+ struct ovsdb_jsonrpc_server **);
+void ovsdb_jsonrpc_server_run(struct ovsdb_jsonrpc_server *);
+void ovsdb_jsonrpc_server_wait(struct ovsdb_jsonrpc_server *);
+
+#endif /* ovsdb/jsonrpc-server.h */
diff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c
new file mode 100644
index 00000000..17a9970e
--- /dev/null
+++ b/ovsdb/ovsdb-server.c
@@ -0,0 +1,223 @@
+/* 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 "ovsdb.h"
+
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+
+#include "command-line.h"
+#include "daemon.h"
+#include "fault.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "jsonrpc-server.h"
+#include "leak-checker.h"
+#include "list.h"
+#include "ovsdb-error.h"
+#include "poll-loop.h"
+#include "process.h"
+#include "stream.h"
+#include "svec.h"
+#include "timeval.h"
+#include "trigger.h"
+#include "util.h"
+#include "unixctl.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ovsdb_server
+
+static const struct jsonrpc_server_cbs ovsdb_jsonrpc_cbs;
+
+static void parse_options(int argc, char *argv[], char **file_namep,
+ struct svec *active, struct svec *passive);
+static void usage(void) NO_RETURN;
+
+static void ovsdb_transact(struct unixctl_conn *, const char *args, void *db);
+
+int
+main(int argc, char *argv[])
+{
+ struct unixctl_server *unixctl;
+ struct ovsdb_jsonrpc_server *jsonrpc;
+ struct svec active, passive;
+ struct ovsdb_error *error;
+ struct ovsdb *db;
+ char *file_name;
+ int retval;
+
+ set_program_name(argv[0]);
+ register_fault_handlers();
+ time_init();
+ vlog_init();
+ signal(SIGPIPE, SIG_IGN);
+ process_init();
+
+ parse_options(argc, argv, &file_name, &active, &passive);
+
+ error = ovsdb_open(file_name, false, &db);
+ if (error) {
+ ovs_fatal(0, "%s", ovsdb_error_to_string(error));
+ }
+
+ retval = ovsdb_jsonrpc_server_create(db, &active, &passive, &jsonrpc);
+ if (retval) {
+ ovs_fatal(retval, "failed to initialize JSON-RPC server for OVSDB");
+ }
+ svec_destroy(&active);
+ svec_destroy(&passive);
+
+ die_if_already_running();
+ daemonize();
+
+ retval = unixctl_server_create(NULL, &unixctl);
+ if (retval) {
+ ovs_fatal(retval, "could not listen for control connections");
+ }
+
+ unixctl_command_register("ovsdb/transact", ovsdb_transact, db);
+
+ for (;;) {
+ ovsdb_jsonrpc_server_run(jsonrpc);
+ unixctl_server_run(unixctl);
+ ovsdb_trigger_run(db, time_msec());
+
+ ovsdb_jsonrpc_server_wait(jsonrpc);
+ unixctl_server_wait(unixctl);
+ ovsdb_trigger_wait(db, time_msec());
+ poll_block();
+ }
+
+ return 0;
+}
+
+static void
+parse_options(int argc, char *argv[], char **file_namep,
+ struct svec *active, struct svec *passive)
+{
+ enum {
+ OPT_DUMMY = UCHAR_MAX + 1,
+ OPT_CONNECT,
+ OPT_LISTEN,
+ VLOG_OPTION_ENUMS,
+ LEAK_CHECKER_OPTION_ENUMS
+ };
+ static struct option long_options[] = {
+ {"connect", required_argument, 0, OPT_CONNECT},
+ {"listen", required_argument, 0, OPT_LISTEN},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'V'},
+ DAEMON_LONG_OPTIONS,
+ VLOG_LONG_OPTIONS,
+ LEAK_CHECKER_LONG_OPTIONS,
+ {0, 0, 0, 0},
+ };
+ char *short_options = long_options_to_short_options(long_options);
+
+ svec_init(active);
+ svec_init(passive);
+ for (;;) {
+ int c;
+
+ c = getopt_long(argc, argv, short_options, long_options, NULL);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case OPT_CONNECT:
+ svec_add(active, optarg);
+ break;
+
+ case OPT_LISTEN:
+ svec_add(passive, optarg);
+ break;
+
+ case 'h':
+ usage();
+
+ case 'V':
+ OVS_PRINT_VERSION(0, 0);
+ exit(EXIT_SUCCESS);
+
+ VLOG_OPTION_HANDLERS
+ DAEMON_OPTION_HANDLERS
+ LEAK_CHECKER_OPTION_HANDLERS
+
+ case '?':
+ exit(EXIT_FAILURE);
+
+ default:
+ abort();
+ }
+ }
+ free(short_options);
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1) {
+ ovs_fatal(0, "database file is only non-option argument; "
+ "use --help for usage");
+ }
+
+ *file_namep = argv[0];
+}
+
+static void
+usage(void)
+{
+ printf("%s: Open vSwitch database server\n"
+ "usage: %s [OPTIONS] DATABASE\n"
+ "where DATABASE is a database file in ovsdb format.\n",
+ program_name, program_name);
+ printf("\nJSON-RPC options (may be specified any number of times):\n"
+ " --connect=REMOTE make active connection to REMOTE\n"
+ " --listen=LOCAL passively listen on LOCAL\n");
+ stream_usage("JSON-RPC", true, true);
+ daemon_usage();
+ vlog_usage();
+ printf("\nOther options:\n"
+ " -h, --help display this help message\n"
+ " -V, --version display version information\n");
+ leak_checker_usage();
+ exit(EXIT_SUCCESS);
+}
+
+static void
+ovsdb_transact(struct unixctl_conn *conn, const char *args, void *db_)
+{
+ struct ovsdb *db = db_;
+ struct json *request, *reply;
+ char *reply_string;
+
+ /* Parse JSON. */
+ request = json_from_string(args);
+ if (request->type == JSON_STRING) {
+ unixctl_command_reply(conn, 501, request->u.string);
+ json_destroy(request);
+ return;
+ }
+
+ /* Execute command. */
+ reply = ovsdb_execute(db, request, 0, NULL);
+ reply_string = json_to_string(reply, 0);
+ unixctl_command_reply(conn, 200, reply_string);
+ free(reply_string);
+ json_destroy(reply);
+}
diff --git a/ovsdb/ovsdb-tool.c b/ovsdb/ovsdb-tool.c
new file mode 100644
index 00000000..5169653d
--- /dev/null
+++ b/ovsdb/ovsdb-tool.c
@@ -0,0 +1,204 @@
+/*
+ * 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 <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "command-line.h"
+#include "compiler.h"
+#include "file.h"
+#include "json.h"
+#include "ovsdb.h"
+#include "ovsdb-error.h"
+#include "table.h"
+#include "timeval.h"
+#include "util.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ovsdb_tool
+
+static const struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+ set_program_name(argv[0]);
+ time_init();
+ vlog_init();
+ parse_options(argc, argv);
+ signal(SIGPIPE, SIG_IGN);
+ run_command(argc - optind, argv + optind, all_commands);
+ return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+ static struct option long_options[] = {
+ {"verbose", optional_argument, 0, 'v'},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+ };
+ char *short_options = long_options_to_short_options(long_options);
+
+ for (;;) {
+ int c;
+
+ c = getopt_long(argc, argv, short_options, long_options, NULL);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'h':
+ usage();
+
+ case 'V':
+ OVS_PRINT_VERSION(0, 0);
+ exit(EXIT_SUCCESS);
+
+ case 'v':
+ vlog_set_verbosity(optarg);
+ break;
+
+ case '?':
+ exit(EXIT_FAILURE);
+
+ default:
+ abort();
+ }
+ }
+ free(short_options);
+}
+
+static void
+usage(void)
+{
+ printf("%s: Open vSwitch database management utility\n"
+ "usage: %s [OPTIONS] COMMAND [ARG...]\n"
+ " create DB SCHEMA create DB with the given SCHEMA\n"
+ " compact DB [DST] compact DB in-place (or to DST)\n"
+ " extract-schema DB print DB's schema on stdout\n"
+ " query DB TRNS execute read-only transaction on DB\n"
+ " transact DB TRNS execute read/write transaction on DB\n",
+ program_name, program_name);
+ vlog_usage();
+ printf("\nOther options:\n"
+ " -h, --help display this help message\n"
+ " -V, --version display version information\n");
+ exit(EXIT_SUCCESS);
+}
+
+static struct json *
+parse_json(const char *s)
+{
+ struct json *json = json_from_string(s);
+ if (json->type == JSON_STRING) {
+ ovs_fatal(0, "\"%s\": %s", s, json->u.string);
+ }
+ return json;
+}
+
+static void
+print_and_free_json(struct json *json)
+{
+ char *string = json_to_string(json, JSSF_SORT);
+ json_destroy(json);
+ puts(string);
+ free(string);
+}
+
+static void
+check_ovsdb_error(struct ovsdb_error *error)
+{
+ if (error) {
+ ovs_fatal(0, "%s", ovsdb_error_to_string(error));
+ }
+}
+
+static void
+do_create(int argc UNUSED, char *argv[])
+{
+ const char *db_file_name = argv[1];
+ const char *schema_file_name = argv[2];
+ struct ovsdb_schema *schema;
+ struct ovsdb_file *db_file;
+ struct json *json;
+
+ /* Read schema from file and convert to JSON. */
+ check_ovsdb_error(ovsdb_schema_from_file(schema_file_name, &schema));
+ json = ovsdb_schema_to_json(schema);
+
+ /* Create database file. */
+ check_ovsdb_error(ovsdb_file_open(db_file_name, O_RDWR | O_CREAT | O_EXCL,
+ &db_file));
+ check_ovsdb_error(ovsdb_file_write(db_file, json));
+ check_ovsdb_error(ovsdb_file_commit(db_file));
+ ovsdb_file_close(db_file);
+
+ json_destroy(json);
+}
+
+static void
+transact(int flags, const char *db_file_name, const char *transaction)
+{
+ struct json *request, *result;
+ struct ovsdb *db;
+
+ check_ovsdb_error(ovsdb_open(db_file_name, flags, &db));
+
+ request = parse_json(transaction);
+ result = ovsdb_execute(db, request, 0, NULL);
+ json_destroy(request);
+
+ print_and_free_json(result);
+ ovsdb_destroy(db);
+}
+
+static void
+do_query(int argc UNUSED, char *argv[])
+{
+ transact(O_RDONLY, argv[1], argv[2]);
+}
+
+static void
+do_transact(int argc UNUSED, char *argv[])
+{
+ transact(O_RDWR, argv[1], argv[2]);
+}
+
+static void
+do_help(int argc UNUSED, char *argv[] UNUSED)
+{
+ usage();
+}
+
+static const struct command all_commands[] = {
+ { "create", 2, 2, do_create },
+ { "query", 2, 2, do_query },
+ { "transact", 2, 2, do_transact },
+ { "help", 0, INT_MAX, do_help },
+ { NULL, 0, 0, NULL },
+};
diff --git a/ovsdb/ovsdb.c b/ovsdb/ovsdb.c
new file mode 100644
index 00000000..e653758f
--- /dev/null
+++ b/ovsdb/ovsdb.c
@@ -0,0 +1,262 @@
+/* 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 "ovsdb.h"
+
+#include <fcntl.h>
+
+#include "file.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "table.h"
+#include "transaction.h"
+
+#define THIS_MODULE VLM_ovsdb
+#include "vlog.h"
+
+struct ovsdb_schema *
+ovsdb_schema_create(const char *name, const char *comment)
+{
+ struct ovsdb_schema *schema;
+
+ schema = xzalloc(sizeof *schema);
+ schema->name = xstrdup(name);
+ schema->comment = comment ? xstrdup(comment) : NULL;
+ shash_init(&schema->tables);
+
+ return schema;
+}
+
+void
+ovsdb_schema_destroy(struct ovsdb_schema *schema)
+{
+ struct shash_node *node;
+
+ SHASH_FOR_EACH (node, &schema->tables) {
+ ovsdb_table_schema_destroy(node->data);
+ }
+ shash_destroy(&schema->tables);
+ free(schema->comment);
+ free(schema->name);
+ free(schema);
+}
+
+struct ovsdb_error *
+ovsdb_schema_from_file(const char *file_name, struct ovsdb_schema **schemap)
+{
+ struct ovsdb_schema *schema;
+ struct ovsdb_error *error;
+ struct json *json;
+
+ *schemap = NULL;
+ json = json_from_file(file_name);
+ if (json->type == JSON_STRING) {
+ error = ovsdb_error("failed to read schema",
+ "\"%s\" could not be read as JSON (%s)",
+ file_name, json_string(json));
+ json_destroy(json);
+ return error;
+ }
+
+ error = ovsdb_schema_from_json(json, &schema);
+ if (error) {
+ json_destroy(json);
+ return ovsdb_wrap_error(error,
+ "failed to parse \"%s\" as ovsdb schema",
+ file_name);
+ }
+
+ *schemap = schema;
+ return NULL;
+}
+
+struct ovsdb_error *
+ovsdb_schema_from_json(struct json *json, struct ovsdb_schema **schemap)
+{
+ struct ovsdb_schema *schema;
+ const struct json *name, *comment, *tables;
+ struct ovsdb_error *error;
+ struct shash_node *node;
+ struct ovsdb_parser parser;
+
+ *schemap = NULL;
+
+ ovsdb_parser_init(&parser, json, "database schema");
+ name = ovsdb_parser_member(&parser, "name", OP_ID);
+ comment = ovsdb_parser_member(&parser, "comment", OP_STRING | OP_OPTIONAL);
+ tables = ovsdb_parser_member(&parser, "tables", OP_OBJECT);
+ error = ovsdb_parser_finish(&parser);
+ if (error) {
+ return error;
+ }
+
+ schema = ovsdb_schema_create(json_string(name),
+ comment ? json_string(comment) : NULL);
+ SHASH_FOR_EACH (node, json_object(tables)) {
+ struct ovsdb_table_schema *table;
+
+ if (node->name[0] == '_') {
+ error = ovsdb_syntax_error(json, NULL, "names beginning with "
+ "\"_\" are reserved");
+ } else {
+ error = ovsdb_table_schema_from_json(node->data, node->name,
+ &table);
+ }
+ if (error) {
+ ovsdb_schema_destroy(schema);
+ return error;
+ }
+
+ shash_add(&schema->tables, table->name, table);
+ }
+ *schemap = schema;
+ return 0;
+}
+
+struct json *
+ovsdb_schema_to_json(const struct ovsdb_schema *schema)
+{
+ struct json *json, *tables;
+ struct shash_node *node;
+
+ json = json_object_create();
+ json_object_put_string(json, "name", schema->name);
+ if (schema->comment) {
+ json_object_put_string(json, "comment", schema->comment);
+ }
+
+ tables = json_object_create();
+
+ SHASH_FOR_EACH (node, &schema->tables) {
+ struct ovsdb_table_schema *table = node->data;
+ json_object_put(tables, table->name,
+ ovsdb_table_schema_to_json(table));
+ }
+ json_object_put(json, "tables", tables);
+
+ return json;
+}
+
+struct ovsdb *
+ovsdb_create(struct ovsdb_file *file, struct ovsdb_schema *schema)
+{
+ struct shash_node *node;
+ struct ovsdb *db;
+
+ db = xmalloc(sizeof *db);
+ db->schema = schema;
+ db->file = file;
+ list_init(&db->triggers);
+ db->run_triggers = false;
+
+ shash_init(&db->tables);
+ SHASH_FOR_EACH (node, &schema->tables) {
+ struct ovsdb_table_schema *ts = node->data;
+ shash_add(&db->tables, node->name, ovsdb_table_create(ts));
+ }
+
+ return db;
+}
+
+struct ovsdb_error *
+ovsdb_open(const char *file_name, bool read_only, struct ovsdb **dbp)
+{
+ struct ovsdb_schema *schema;
+ struct ovsdb_error *error;
+ struct ovsdb_file *file;
+ struct json *json;
+ struct ovsdb *db;
+
+ error = ovsdb_file_open(file_name, read_only ? O_RDONLY : O_RDWR, &file);
+ if (error) {
+ return error;
+ }
+
+ error = ovsdb_file_read(file, &json);
+ if (error) {
+ return error;
+ } else if (!json) {
+ return ovsdb_io_error(EOF, "%s: database file contains no schema",
+ file_name);
+ }
+
+ error = ovsdb_schema_from_json(json, &schema);
+ if (error) {
+ json_destroy(json);
+ return ovsdb_wrap_error(error,
+ "failed to parse \"%s\" as ovsdb schema",
+ file_name);
+ }
+ json_destroy(json);
+
+ db = ovsdb_create(read_only ? file : NULL, schema);
+ while ((error = ovsdb_file_read(file, &json)) == NULL && json) {
+ struct ovsdb_txn *txn;
+
+ error = ovsdb_txn_from_json(db, json, &txn);
+ json_destroy(json);
+ if (error) {
+ break;
+ }
+
+ ovsdb_txn_commit(txn);
+ }
+ if (error) {
+ char *msg = ovsdb_error_to_string(error);
+ VLOG_WARN("%s", msg);
+ free(msg);
+
+ ovsdb_error_destroy(error);
+ }
+
+ if (read_only) {
+ ovsdb_file_close(file);
+ }
+
+ *dbp = db;
+ return NULL;
+}
+
+void
+ovsdb_destroy(struct ovsdb *db)
+{
+ if (db) {
+ struct shash_node *node;
+
+ /* Delete all the tables. This also deletes their schemas. */
+ SHASH_FOR_EACH (node, &db->tables) {
+ struct ovsdb_table *table = node->data;
+ ovsdb_table_destroy(table);
+ }
+ shash_destroy(&db->tables);
+
+ /* Clear the schema's hash of table schemas. The schemas, but not the
+ * table that points to them, were deleted in the previous step. */
+ shash_destroy(&db->schema->tables);
+
+ ovsdb_schema_destroy(db->schema);
+ ovsdb_file_close(db->file);
+ free(db);
+ }
+}
+
+struct ovsdb_table *
+ovsdb_get_table(const struct ovsdb *db, const char *name)
+{
+ return shash_find_data(&db->tables, name);
+}
diff --git a/ovsdb/ovsdb.h b/ovsdb/ovsdb.h
new file mode 100644
index 00000000..3f62966a
--- /dev/null
+++ b/ovsdb/ovsdb.h
@@ -0,0 +1,73 @@
+/* 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_OVSDB_H
+#define OVSDB_OVSDB_H 1
+
+#include "compiler.h"
+#include "hmap.h"
+#include "list.h"
+#include "shash.h"
+
+struct json;
+struct uuid;
+
+/* Database schema. */
+struct ovsdb_schema {
+ char *name;
+ char *comment;
+ struct shash tables; /* Contains "struct ovsdb_table_schema *"s. */
+};
+
+struct ovsdb_schema *ovsdb_schema_create(const char *name,
+ const char *comment);
+void ovsdb_schema_destroy(struct ovsdb_schema *);
+
+struct ovsdb_error *ovsdb_schema_from_file(const char *file_name,
+ struct ovsdb_schema **)
+ WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_schema_from_json(struct json *,
+ struct ovsdb_schema **)
+ WARN_UNUSED_RESULT;
+struct json *ovsdb_schema_to_json(const struct ovsdb_schema *);
+
+/* Database. */
+struct ovsdb {
+ struct ovsdb_schema *schema;
+ struct ovsdb_file *file; /* Disk file (null for in-memory db). */
+ struct shash tables; /* Contains "struct ovsdb_table *"s. */
+
+ /* Triggers. */
+ struct list triggers; /* Contains "struct ovsdb_trigger"s. */
+ bool run_triggers;
+};
+
+struct ovsdb *ovsdb_create(struct ovsdb_file *, struct ovsdb_schema *);
+struct ovsdb_error *ovsdb_open(const char *file_name, bool read_only,
+ struct ovsdb **)
+ WARN_UNUSED_RESULT;
+void ovsdb_destroy(struct ovsdb *);
+
+struct ovsdb_error *ovsdb_from_json(const struct json *, struct ovsdb **)
+ WARN_UNUSED_RESULT;
+struct json *ovsdb_to_json(const struct ovsdb *);
+
+struct ovsdb_table *ovsdb_get_table(const struct ovsdb *, const char *);
+
+struct json *ovsdb_execute(struct ovsdb *, const struct json *params,
+ long long int elapsed_msec,
+ long long int *timeout_msec);
+
+#endif /* ovsdb/ovsdb.h */
diff --git a/ovsdb/query.c b/ovsdb/query.c
new file mode 100644
index 00000000..878ac5b2
--- /dev/null
+++ b/ovsdb/query.c
@@ -0,0 +1,99 @@
+/* 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 "query.h"
+
+#include "column.h"
+#include "condition.h"
+#include "row.h"
+#include "table.h"
+
+void
+ovsdb_query(struct ovsdb_table *table, const struct ovsdb_condition *cnd,
+ bool (*output_row)(const struct ovsdb_row *, void *aux), void *aux)
+{
+ if (cnd->n_clauses > 0
+ && cnd->clauses[0].column->index == OVSDB_COL_UUID
+ && cnd->clauses[0].function == OVSDB_F_EQ) {
+ /* Optimize the case where the query has a clause of the form "uuid ==
+ * <some-uuid>", since we have an index on UUID. */
+ const struct ovsdb_row *row;
+
+ row = ovsdb_table_get_row(table, &cnd->clauses[0].arg.keys[0].uuid);
+ if (row && row->table == table && ovsdb_condition_evaluate(row, cnd)) {
+ output_row(row, aux);
+ }
+ } else {
+ /* Linear scan. */
+ const struct ovsdb_row *row, *next;
+
+ HMAP_FOR_EACH_SAFE (row, next, struct ovsdb_row, hmap_node,
+ &table->rows) {
+ if (ovsdb_condition_evaluate(row, cnd) && !output_row(row, aux)) {
+ break;
+ }
+ }
+ }
+}
+
+static bool
+query_row_set_cb(const struct ovsdb_row *row, void *results_)
+{
+ struct ovsdb_row_set *results = results_;
+ ovsdb_row_set_add_row(results, row);
+ return true;
+}
+
+void
+ovsdb_query_row_set(struct ovsdb_table *table,
+ const struct ovsdb_condition *condition,
+ struct ovsdb_row_set *results)
+{
+ ovsdb_query(table, condition, query_row_set_cb, results);
+}
+
+static bool
+query_distinct_cb(const struct ovsdb_row *row, void *hash_)
+{
+ struct ovsdb_row_hash *hash = hash_;
+ ovsdb_row_hash_insert(hash, row);
+ return true;
+}
+
+void
+ovsdb_query_distinct(struct ovsdb_table *table,
+ const struct ovsdb_condition *condition,
+ const struct ovsdb_column_set *columns,
+ struct ovsdb_row_set *results)
+{
+ if (!columns || ovsdb_column_set_contains(columns, OVSDB_COL_UUID)) {
+ /* All the result rows are guaranteed to be distinct anyway. */
+ return ovsdb_query_row_set(table, condition, results);
+ } else {
+ /* Use hash table to drop duplicates. */
+ struct ovsdb_row_hash_node *node;
+ struct ovsdb_row_hash hash;
+
+ ovsdb_row_hash_init(&hash, columns);
+ ovsdb_query(table, condition, query_distinct_cb, &hash);
+ HMAP_FOR_EACH (node, struct ovsdb_row_hash_node, hmap_node,
+ &hash.rows) {
+ ovsdb_row_set_add_row(results, node->row);
+ }
+ ovsdb_row_hash_destroy(&hash, false);
+ }
+}
diff --git a/ovsdb/query.h b/ovsdb/query.h
new file mode 100644
index 00000000..f5cfe2e6
--- /dev/null
+++ b/ovsdb/query.h
@@ -0,0 +1,37 @@
+/* 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_QUERY_H
+#define OVSDB_QUERY_H 1
+
+#include <stdbool.h>
+
+struct ovsdb_column_set;
+struct ovsdb_condition;
+struct ovsdb_row;
+struct ovsdb_row_set;
+struct ovsdb_table;
+struct ovsdb_txn;
+
+void ovsdb_query(struct ovsdb_table *, const struct ovsdb_condition *,
+ bool (*output_row)(const struct ovsdb_row *, void *aux),
+ void *aux);
+void ovsdb_query_row_set(struct ovsdb_table *, const struct ovsdb_condition *,
+ struct ovsdb_row_set *);
+void ovsdb_query_distinct(struct ovsdb_table *, const struct ovsdb_condition *,
+ const struct ovsdb_column_set *,
+ struct ovsdb_row_set *);
+
+#endif /* ovsdb/query.h */
diff --git a/ovsdb/row.c b/ovsdb/row.c
new file mode 100644
index 00000000..1b819420
--- /dev/null
+++ b/ovsdb/row.c
@@ -0,0 +1,386 @@
+/* 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 "row.h"
+
+#include <assert.h>
+#include <stddef.h>
+
+#include "json.h"
+#include "ovsdb-error.h"
+#include "shash.h"
+#include "sort.h"
+#include "table.h"
+
+static struct ovsdb_row *
+allocate_row(const struct ovsdb_table *table)
+{
+ size_t n_fields = shash_count(&table->schema->columns);
+ size_t row_size = (offsetof(struct ovsdb_row, fields)
+ + sizeof(struct ovsdb_datum) * n_fields);
+ struct ovsdb_row *row = xmalloc(row_size);
+ row->table = (struct ovsdb_table *) table;
+ row->txn_row = NULL;
+ return row;
+}
+
+struct ovsdb_row *
+ovsdb_row_create(const struct ovsdb_table *table)
+{
+ struct shash_node *node;
+ struct ovsdb_row *row;
+
+ row = allocate_row(table);
+ SHASH_FOR_EACH (node, &table->schema->columns) {
+ const struct ovsdb_column *column = node->data;
+ ovsdb_datum_init_default(&row->fields[column->index], &column->type);
+ }
+ return row;
+}
+
+struct ovsdb_row *
+ovsdb_row_clone(const struct ovsdb_row *old)
+{
+ const struct ovsdb_table *table = old->table;
+ const struct shash_node *node;
+ struct ovsdb_row *new;
+
+ new = allocate_row(table);
+ SHASH_FOR_EACH (node, &table->schema->columns) {
+ const struct ovsdb_column *column = node->data;
+ ovsdb_datum_clone(&new->fields[column->index],
+ &old->fields[column->index],
+ &column->type);
+ }
+ return new;
+}
+
+/* The caller is responsible for ensuring that 'row' has been removed from its
+ * table and that it is not participating in a transaction. */
+void
+ovsdb_row_destroy(struct ovsdb_row *row)
+{
+ if (row) {
+ const struct ovsdb_table *table = row->table;
+ const struct shash_node *node;
+
+ SHASH_FOR_EACH (node, &table->schema->columns) {
+ const struct ovsdb_column *column = node->data;
+ ovsdb_datum_destroy(&row->fields[column->index], &column->type);
+ }
+ free(row);
+ }
+}
+
+uint32_t
+ovsdb_row_hash_columns(const struct ovsdb_row *row,
+ const struct ovsdb_column_set *columns,
+ uint32_t basis)
+{
+ size_t i;
+
+ for (i = 0; i < columns->n_columns; i++) {
+ const struct ovsdb_column *column = columns->columns[i];
+ basis = ovsdb_datum_hash(&row->fields[column->index], &column->type,
+ basis);
+ }
+
+ return basis;
+}
+
+int
+ovsdb_row_compare_columns_3way(const struct ovsdb_row *a,
+ const struct ovsdb_row *b,
+ const struct ovsdb_column_set *columns)
+{
+ size_t i;
+
+ for (i = 0; i < columns->n_columns; i++) {
+ const struct ovsdb_column *column = columns->columns[i];
+ int cmp = ovsdb_datum_compare_3way(&a->fields[column->index],
+ &b->fields[column->index],
+ &column->type);
+ if (cmp) {
+ return cmp;
+ }
+ }
+
+ return 0;
+}
+
+bool
+ovsdb_row_equal_columns(const struct ovsdb_row *a,
+ const struct ovsdb_row *b,
+ const struct ovsdb_column_set *columns)
+{
+ size_t i;
+
+ for (i = 0; i < columns->n_columns; i++) {
+ const struct ovsdb_column *column = columns->columns[i];
+ if (!ovsdb_datum_equals(&a->fields[column->index],
+ &b->fields[column->index],
+ &column->type)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void
+ovsdb_row_update_columns(struct ovsdb_row *dst,
+ const struct ovsdb_row *src,
+ const struct ovsdb_column_set *columns)
+{
+ size_t i;
+
+ for (i = 0; i < columns->n_columns; i++) {
+ const struct ovsdb_column *column = columns->columns[i];
+ ovsdb_datum_destroy(&dst->fields[column->index], &column->type);
+ ovsdb_datum_clone(&dst->fields[column->index],
+ &src->fields[column->index],
+ &column->type);
+ }
+}
+
+struct ovsdb_error *
+ovsdb_row_from_json(struct ovsdb_row *row, const struct json *json,
+ const struct ovsdb_symbol_table *symtab,
+ struct ovsdb_column_set *included)
+{
+ struct ovsdb_table_schema *schema = row->table->schema;
+ struct ovsdb_error *error;
+ struct shash_node *node;
+
+ if (json->type != JSON_OBJECT) {
+ return ovsdb_syntax_error(json, NULL, "row must be JSON object");
+ }
+
+ SHASH_FOR_EACH (node, json_object(json)) {
+ const char *column_name = node->name;
+ const struct ovsdb_column *column;
+ struct ovsdb_datum datum;
+
+ column = ovsdb_table_schema_get_column(schema, column_name);
+ if (!column) {
+ return ovsdb_syntax_error(json, "unknown column",
+ "No column %s in table %s.",
+ column_name, schema->name);
+ }
+
+ error = ovsdb_datum_from_json(&datum, &column->type, node->data,
+ symtab);
+ if (error) {
+ return error;
+ }
+ ovsdb_datum_swap(&row->fields[column->index], &datum);
+ ovsdb_datum_destroy(&datum, &column->type);
+ if (included) {
+ ovsdb_column_set_add(included, column);
+ }
+ }
+
+ return NULL;
+}
+
+static void
+put_json_column(struct json *object, const struct ovsdb_row *row,
+ const struct ovsdb_column *column)
+{
+ json_object_put(object, column->name,
+ ovsdb_datum_to_json(&row->fields[column->index],
+ &column->type));
+}
+
+struct json *
+ovsdb_row_to_json(const struct ovsdb_row *row,
+ const struct ovsdb_column_set *columns)
+{
+ struct json *json;
+ size_t i;
+
+ json = json_object_create();
+ for (i = 0; i < columns->n_columns; i++) {
+ put_json_column(json, row, columns->columns[i]);
+ }
+ return json;
+}
+
+void
+ovsdb_row_set_init(struct ovsdb_row_set *set)
+{
+ set->rows = NULL;
+ set->n_rows = set->allocated_rows = 0;
+}
+
+void
+ovsdb_row_set_destroy(struct ovsdb_row_set *set)
+{
+ free(set->rows);
+}
+
+void
+ovsdb_row_set_add_row(struct ovsdb_row_set *set, const struct ovsdb_row *row)
+{
+ if (set->n_rows >= set->allocated_rows) {
+ set->rows = x2nrealloc(set->rows, &set->allocated_rows,
+ sizeof *set->rows);
+ }
+ set->rows[set->n_rows++] = row;
+}
+
+struct json *
+ovsdb_row_set_to_json(const struct ovsdb_row_set *rows,
+ const struct ovsdb_column_set *columns)
+{
+ struct json **json_rows;
+ size_t i;
+
+ json_rows = xmalloc(rows->n_rows * sizeof *json_rows);
+ for (i = 0; i < rows->n_rows; i++) {
+ json_rows[i] = ovsdb_row_to_json(rows->rows[i], columns);
+ }
+ return json_array_create(json_rows, rows->n_rows);
+}
+
+struct ovsdb_row_set_sort_cbdata {
+ struct ovsdb_row_set *set;
+ const struct ovsdb_column_set *columns;
+};
+
+static int
+ovsdb_row_set_sort_compare_cb(size_t a, size_t b, void *cbdata_)
+{
+ struct ovsdb_row_set_sort_cbdata *cbdata = cbdata_;
+ return ovsdb_row_compare_columns_3way(cbdata->set->rows[a],
+ cbdata->set->rows[b],
+ cbdata->columns);
+}
+
+static void
+ovsdb_row_set_sort_swap_cb(size_t a, size_t b, void *cbdata_)
+{
+ struct ovsdb_row_set_sort_cbdata *cbdata = cbdata_;
+ const struct ovsdb_row *tmp = cbdata->set->rows[a];
+ cbdata->set->rows[a] = cbdata->set->rows[b];
+ cbdata->set->rows[b] = tmp;
+}
+
+void
+ovsdb_row_set_sort(struct ovsdb_row_set *set,
+ const struct ovsdb_column_set *columns)
+{
+ if (columns && columns->n_columns && set->n_rows > 1) {
+ struct ovsdb_row_set_sort_cbdata cbdata;
+ cbdata.set = set;
+ cbdata.columns = columns;
+ sort(set->n_rows,
+ ovsdb_row_set_sort_compare_cb,
+ ovsdb_row_set_sort_swap_cb,
+ &cbdata);
+ }
+}
+
+void
+ovsdb_row_hash_init(struct ovsdb_row_hash *rh,
+ const struct ovsdb_column_set *columns)
+{
+ hmap_init(&rh->rows);
+ ovsdb_column_set_clone(&rh->columns, columns);
+}
+
+void
+ovsdb_row_hash_destroy(struct ovsdb_row_hash *rh, bool destroy_rows)
+{
+ struct ovsdb_row_hash_node *node, *next;
+
+ HMAP_FOR_EACH_SAFE (node, next, struct ovsdb_row_hash_node, hmap_node,
+ &rh->rows) {
+ hmap_remove(&rh->rows, &node->hmap_node);
+ if (destroy_rows) {
+ ovsdb_row_destroy((struct ovsdb_row *) node->row);
+ }
+ free(node);
+ }
+ hmap_destroy(&rh->rows);
+ ovsdb_column_set_destroy(&rh->columns);
+}
+
+size_t
+ovsdb_row_hash_count(const struct ovsdb_row_hash *rh)
+{
+ return hmap_count(&rh->rows);
+}
+
+bool
+ovsdb_row_hash_contains(const struct ovsdb_row_hash *rh,
+ const struct ovsdb_row *row)
+{
+ size_t hash = ovsdb_row_hash_columns(row, &rh->columns, 0);
+ return ovsdb_row_hash_contains__(rh, row, hash);
+}
+
+/* Returns true if every row in 'b' has an equal row in 'a'. */
+bool
+ovsdb_row_hash_contains_all(const struct ovsdb_row_hash *a,
+ const struct ovsdb_row_hash *b)
+{
+ struct ovsdb_row_hash_node *node;
+
+ assert(ovsdb_column_set_equals(&a->columns, &b->columns));
+ HMAP_FOR_EACH (node, struct ovsdb_row_hash_node, hmap_node, &b->rows) {
+ if (!ovsdb_row_hash_contains__(a, node->row, node->hmap_node.hash)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+ovsdb_row_hash_insert(struct ovsdb_row_hash *rh, const struct ovsdb_row *row)
+{
+ size_t hash = ovsdb_row_hash_columns(row, &rh->columns, 0);
+ return ovsdb_row_hash_insert__(rh, row, hash);
+}
+
+bool
+ovsdb_row_hash_contains__(const struct ovsdb_row_hash *rh,
+ const struct ovsdb_row *row, size_t hash)
+{
+ struct ovsdb_row_hash_node *node;
+ HMAP_FOR_EACH_WITH_HASH (node, struct ovsdb_row_hash_node, hmap_node,
+ hash, &rh->rows) {
+ if (ovsdb_row_equal_columns(row, node->row, &rh->columns)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+ovsdb_row_hash_insert__(struct ovsdb_row_hash *rh, const struct ovsdb_row *row,
+ size_t hash)
+{
+ if (!ovsdb_row_hash_contains__(rh, row, hash)) {
+ struct ovsdb_row_hash_node *node = xmalloc(sizeof *node);
+ node->row = row;
+ hmap_insert(&rh->rows, &node->hmap_node, hash);
+ return true;
+ } else {
+ return false;
+ }
+}
diff --git a/ovsdb/row.h b/ovsdb/row.h
new file mode 100644
index 00000000..55c4f142
--- /dev/null
+++ b/ovsdb/row.h
@@ -0,0 +1,139 @@
+/* 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_ROW_H
+#define OVSDB_ROW_H 1
+
+#include <stddef.h>
+#include <stdint.h>
+#include "column.h"
+#include "hmap.h"
+#include "ovsdb-data.h"
+
+struct ovsdb_column_set;
+
+/* A row in a database table. */
+struct ovsdb_row {
+ struct ovsdb_table *table; /* Table to which this belongs. */
+ struct hmap_node hmap_node; /* Element in ovsdb_table's 'rows' hmap. */
+ struct ovsdb_txn_row *txn_row; /* Transaction that row is in, if any. */
+ struct ovsdb_datum fields[];
+};
+
+struct ovsdb_row *ovsdb_row_create(const struct ovsdb_table *);
+struct ovsdb_row *ovsdb_row_clone(const struct ovsdb_row *);
+void ovsdb_row_destroy(struct ovsdb_row *);
+
+uint32_t ovsdb_row_hash_columns(const struct ovsdb_row *,
+ const struct ovsdb_column_set *,
+ uint32_t basis);
+bool ovsdb_row_equal_columns(const struct ovsdb_row *,
+ const struct ovsdb_row *,
+ const struct ovsdb_column_set *);
+int ovsdb_row_compare_columns_3way(const struct ovsdb_row *,
+ const struct ovsdb_row *,
+ const struct ovsdb_column_set *);
+void ovsdb_row_update_columns(struct ovsdb_row *, const struct ovsdb_row *,
+ const struct ovsdb_column_set *);
+
+struct ovsdb_error *ovsdb_row_from_json(struct ovsdb_row *,
+ const struct json *,
+ const struct ovsdb_symbol_table *,
+ struct ovsdb_column_set *included)
+ WARN_UNUSED_RESULT;
+struct json *ovsdb_row_to_json(const struct ovsdb_row *,
+ const struct ovsdb_column_set *include);
+
+static inline const struct uuid *
+ovsdb_row_get_uuid(const struct ovsdb_row *row)
+{
+ return &row->fields[OVSDB_COL_UUID].keys[0].uuid;
+}
+
+static inline struct uuid *
+ovsdb_row_get_uuid_rw(struct ovsdb_row *row)
+{
+ return &row->fields[OVSDB_COL_UUID].keys[0].uuid;
+}
+
+static inline const struct uuid *
+ovsdb_row_get_version(const struct ovsdb_row *row)
+{
+ return &row->fields[OVSDB_COL_VERSION].keys[0].uuid;
+}
+
+static inline struct uuid *
+ovsdb_row_get_version_rw(struct ovsdb_row *row)
+{
+ return &row->fields[OVSDB_COL_VERSION].keys[0].uuid;
+}
+
+static inline uint32_t
+ovsdb_row_hash(const struct ovsdb_row *row)
+{
+ return uuid_hash(ovsdb_row_get_uuid(row));
+}
+
+/* An unordered collection of rows. */
+struct ovsdb_row_set {
+ const struct ovsdb_row **rows;
+ size_t n_rows, allocated_rows;
+};
+
+#define OVSDB_ROW_SET_INITIALIZER { NULL, 0, 0 }
+
+void ovsdb_row_set_init(struct ovsdb_row_set *);
+void ovsdb_row_set_destroy(struct ovsdb_row_set *);
+void ovsdb_row_set_add_row(struct ovsdb_row_set *, const struct ovsdb_row *);
+
+struct json *ovsdb_row_set_to_json(const struct ovsdb_row_set *,
+ const struct ovsdb_column_set *);
+
+void ovsdb_row_set_sort(struct ovsdb_row_set *,
+ const struct ovsdb_column_set *);
+
+/* A hash table of rows. A specified set of columns is used for hashing and
+ * comparing rows.
+ *
+ * The row hash doesn't necessarily own its rows. They may be owned by, for
+ * example, an ovsdb_table. */
+struct ovsdb_row_hash {
+ struct hmap rows;
+ struct ovsdb_column_set columns;
+};
+
+#define OVSDB_ROW_HASH_INITIALIZER(RH) \
+ { HMAP_INITIALIZER(&(RH).rows), OVSDB_COLUMN_SET_INITIALIZER }
+
+struct ovsdb_row_hash_node {
+ struct hmap_node hmap_node;
+ const struct ovsdb_row *row;
+};
+
+void ovsdb_row_hash_init(struct ovsdb_row_hash *,
+ const struct ovsdb_column_set *);
+void ovsdb_row_hash_destroy(struct ovsdb_row_hash *, bool destroy_rows);
+size_t ovsdb_row_hash_count(const struct ovsdb_row_hash *);
+bool ovsdb_row_hash_contains(const struct ovsdb_row_hash *,
+ const struct ovsdb_row *);
+bool ovsdb_row_hash_contains_all(const struct ovsdb_row_hash *,
+ const struct ovsdb_row_hash *);
+bool ovsdb_row_hash_insert(struct ovsdb_row_hash *, const struct ovsdb_row *);
+bool ovsdb_row_hash_contains__(const struct ovsdb_row_hash *,
+ const struct ovsdb_row *, size_t hash);
+bool ovsdb_row_hash_insert__(struct ovsdb_row_hash *,
+ const struct ovsdb_row *, size_t hash);
+
+#endif /* ovsdb/row.h */
diff --git a/ovsdb/table.c b/ovsdb/table.c
new file mode 100644
index 00000000..d017a6ba
--- /dev/null
+++ b/ovsdb/table.c
@@ -0,0 +1,228 @@
+/* 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 "table.h"
+
+#include <assert.h>
+
+#include "json.h"
+#include "column.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "ovsdb-types.h"
+#include "row.h"
+
+static void
+add_column(struct ovsdb_table_schema *ts, struct ovsdb_column *column)
+{
+ assert(!shash_find(&ts->columns, column->name));
+ column->index = shash_count(&ts->columns);
+ shash_add(&ts->columns, column->name, column);
+}
+
+struct ovsdb_table_schema *
+ovsdb_table_schema_create(const char *name, const char *comment, bool mutable)
+{
+ struct ovsdb_column *uuid, *version;
+ struct ovsdb_table_schema *ts;
+
+ ts = xzalloc(sizeof *ts);
+ ts->name = xstrdup(name);
+ ts->comment = comment ? xstrdup(comment) : NULL;
+ ts->mutable = mutable;
+ shash_init(&ts->columns);
+
+ uuid = ovsdb_column_create(
+ "_uuid", "Unique identifier for this row.",
+ false, true, &ovsdb_type_uuid);
+ add_column(ts, uuid);
+ assert(uuid->index == OVSDB_COL_UUID);
+
+ version = ovsdb_column_create(
+ "_version", "Unique identifier for this version of this row.",
+ false, false, &ovsdb_type_uuid);
+ add_column(ts, version);
+ assert(version->index == OVSDB_COL_VERSION);
+
+ return ts;
+}
+
+void
+ovsdb_table_schema_destroy(struct ovsdb_table_schema *ts)
+{
+ struct shash_node *node;
+
+ SHASH_FOR_EACH (node, &ts->columns) {
+ ovsdb_column_destroy(node->data);
+ }
+ shash_destroy(&ts->columns);
+ free(ts->comment);
+ free(ts->name);
+ free(ts);
+}
+
+struct ovsdb_error *
+ovsdb_table_schema_from_json(const struct json *json, const char *name,
+ struct ovsdb_table_schema **tsp)
+{
+ struct ovsdb_table_schema *ts;
+ const struct json *comment, *columns, *mutable;
+ struct shash_node *node;
+ struct ovsdb_parser parser;
+ struct ovsdb_error *error;
+
+ *tsp = NULL;
+
+ ovsdb_parser_init(&parser, json, "table schema for table %s", name);
+ comment = ovsdb_parser_member(&parser, "comment", OP_STRING | OP_OPTIONAL);
+ columns = ovsdb_parser_member(&parser, "columns", OP_OBJECT);
+ mutable = ovsdb_parser_member(&parser, "mutable",
+ OP_TRUE | OP_FALSE | OP_OPTIONAL);
+ error = ovsdb_parser_finish(&parser);
+ if (error) {
+ return error;
+ }
+
+ if (shash_is_empty(json_object(columns))) {
+ return ovsdb_syntax_error(json, NULL,
+ "table must have at least one column");
+ }
+
+ ts = ovsdb_table_schema_create(name,
+ comment ? json_string(comment) : NULL,
+ mutable ? json_boolean(mutable) : true);
+ SHASH_FOR_EACH (node, json_object(columns)) {
+ struct ovsdb_column *column;
+
+ if (node->name[0] == '_') {
+ error = ovsdb_syntax_error(json, NULL, "names beginning with "
+ "\"_\" are reserved");
+ } else {
+ error = ovsdb_column_from_json(node->data, node->name, &column);
+ }
+ if (error) {
+ ovsdb_table_schema_destroy(ts);
+ return error;
+ }
+
+ add_column(ts, column);
+ }
+ *tsp = ts;
+ return 0;
+}
+
+struct json *
+ovsdb_table_schema_to_json(const struct ovsdb_table_schema *ts)
+{
+ struct json *json, *columns;
+ struct shash_node *node;
+
+ json = json_object_create();
+ if (ts->comment) {
+ json_object_put_string(json, "comment", ts->comment);
+ }
+ if (!ts->mutable) {
+ json_object_put(json, "mutable", json_boolean_create(false));
+ }
+
+ columns = json_object_create();
+
+ SHASH_FOR_EACH (node, &ts->columns) {
+ struct ovsdb_column *column = node->data;
+ if (node->name[0] != '_') {
+ json_object_put(columns, column->name,
+ ovsdb_column_to_json(column));
+ }
+ }
+ json_object_put(json, "columns", columns);
+
+ return json;
+}
+
+const struct ovsdb_column *
+ovsdb_table_schema_get_column(const struct ovsdb_table_schema *ts,
+ const char *name)
+{
+ return shash_find_data(&ts->columns, name);
+}
+
+struct ovsdb_table *
+ovsdb_table_create(struct ovsdb_table_schema *ts)
+{
+ struct ovsdb_table *table;
+
+ table = xmalloc(sizeof *table);
+ table->schema = ts;
+ hmap_init(&table->rows);
+
+ return table;
+}
+
+void
+ovsdb_table_destroy(struct ovsdb_table *table)
+{
+ if (table) {
+ struct ovsdb_row *row, *next;
+
+ HMAP_FOR_EACH_SAFE (row, next, struct ovsdb_row, hmap_node,
+ &table->rows) {
+ ovsdb_row_destroy(row);
+ }
+ hmap_destroy(&table->rows);
+
+ ovsdb_table_schema_destroy(table->schema);
+ free(table);
+ }
+}
+
+static const struct ovsdb_row *
+ovsdb_table_get_row__(const struct ovsdb_table *table, const struct uuid *uuid,
+ size_t hash)
+{
+ struct ovsdb_row *row;
+
+ HMAP_FOR_EACH_WITH_HASH (row, struct ovsdb_row, hmap_node, hash,
+ &table->rows) {
+ if (uuid_equals(ovsdb_row_get_uuid(row), uuid)) {
+ return row;
+ }
+ }
+
+ return NULL;
+}
+
+const struct ovsdb_row *
+ovsdb_table_get_row(const struct ovsdb_table *table, const struct uuid *uuid)
+{
+ return ovsdb_table_get_row__(table, uuid, uuid_hash(uuid));
+}
+
+/* This is probably not the function you want. Use ovsdb_txn_row_modify()
+ * instead. */
+bool
+ovsdb_table_put_row(struct ovsdb_table *table, struct ovsdb_row *row)
+{
+ const struct uuid *uuid = ovsdb_row_get_uuid(row);
+ size_t hash = uuid_hash(uuid);
+
+ if (!ovsdb_table_get_row__(table, uuid, hash)) {
+ hmap_insert(&table->rows, &row->hmap_node, hash);
+ return true;
+ } else {
+ return false;
+ }
+}
diff --git a/ovsdb/table.h b/ovsdb/table.h
new file mode 100644
index 00000000..9e36c911
--- /dev/null
+++ b/ovsdb/table.h
@@ -0,0 +1,63 @@
+/* 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_TABLE_H
+#define OVSDB_TABLE_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+#include "hmap.h"
+#include "shash.h"
+
+struct json;
+struct uuid;
+
+/* Schema for a database table. */
+struct ovsdb_table_schema {
+ char *name;
+ char *comment;
+ bool mutable;
+ struct shash columns; /* Contains "struct ovsdb_column *"s. */
+};
+
+struct ovsdb_table_schema *ovsdb_table_schema_create(const char *name,
+ const char *comment,
+ bool mutable);
+void ovsdb_table_schema_destroy(struct ovsdb_table_schema *);
+
+struct ovsdb_error *ovsdb_table_schema_from_json(const struct json *,
+ const char *name,
+ struct ovsdb_table_schema **)
+ WARN_UNUSED_RESULT;
+struct json *ovsdb_table_schema_to_json(const struct ovsdb_table_schema *);
+
+const struct ovsdb_column *ovsdb_table_schema_get_column(
+ const struct ovsdb_table_schema *, const char *name);
+
+/* Database table. */
+
+struct ovsdb_table {
+ struct ovsdb_table_schema *schema;
+ struct hmap rows; /* Contains "struct ovsdb_row"s. */
+};
+
+struct ovsdb_table *ovsdb_table_create(struct ovsdb_table_schema *);
+void ovsdb_table_destroy(struct ovsdb_table *);
+
+const struct ovsdb_row *ovsdb_table_get_row(const struct ovsdb_table *,
+ const struct uuid *);
+bool ovsdb_table_put_row(struct ovsdb_table *, struct ovsdb_row *);
+
+#endif /* ovsdb/table.h */
diff --git a/ovsdb/transaction.c b/ovsdb/transaction.c
new file mode 100644
index 00000000..21a46ec7
--- /dev/null
+++ b/ovsdb/transaction.c
@@ -0,0 +1,444 @@
+/* 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 "transaction.h"
+
+#include <assert.h>
+
+#include "hash.h"
+#include "hmap.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "ovsdb.h"
+#include "row.h"
+#include "table.h"
+#include "uuid.h"
+
+struct ovsdb_txn {
+ struct ovsdb *db;
+ struct hmap txn_tables; /* Contains "struct ovsdb_txn_table"s. */
+};
+
+/* A table modified by a transaction. */
+struct ovsdb_txn_table {
+ struct hmap_node hmap_node; /* Element in ovsdb_txn's txn_tables hmap. */
+ struct ovsdb_table *table;
+ struct hmap txn_rows; /* Contains "struct ovsdb_txn_row"s. */
+};
+
+/* A row modified by the transaction:
+ *
+ * - A row added by a transaction will have null 'old' and non-null 'new'.
+ *
+ * - A row deleted by a transaction will have non-null 'old' and null
+ * 'new'.
+ *
+ * - A row modified by a transaction will have non-null 'old' and 'new'.
+ *
+ * - 'old' and 'new' both null is invalid. It would indicate that a row
+ * was added then deleted within a single transaction, but we instead
+ * handle that case by deleting the txn_row entirely.
+ */
+struct ovsdb_txn_row {
+ struct hmap_node hmap_node; /* In ovsdb_txn_table's txn_rows hmap. */
+ struct ovsdb_row *old; /* The old row. */
+ struct ovsdb_row *new; /* The new row. */
+};
+
+static const struct uuid *
+ovsdb_txn_row_get_uuid(const struct ovsdb_txn_row *txn_row)
+{
+ const struct ovsdb_row *row = txn_row->old ? txn_row->old : txn_row->new;
+ return ovsdb_row_get_uuid(row);
+}
+
+struct ovsdb_txn *
+ovsdb_txn_create(struct ovsdb *db)
+{
+ struct ovsdb_txn *txn = xmalloc(sizeof *txn);
+ txn->db = db;
+ hmap_init(&txn->txn_tables);
+ return txn;
+}
+
+static void
+ovsdb_txn_destroy(struct ovsdb_txn *txn, void (*cb)(struct ovsdb_txn_row *))
+{
+ struct ovsdb_txn_table *txn_table, *next_txn_table;
+
+ HMAP_FOR_EACH_SAFE (txn_table, next_txn_table,
+ struct ovsdb_txn_table, hmap_node, &txn->txn_tables)
+ {
+ struct ovsdb_txn_row *txn_row, *next_txn_row;
+
+ HMAP_FOR_EACH_SAFE (txn_row, next_txn_row,
+ struct ovsdb_txn_row, hmap_node,
+ &txn_table->txn_rows)
+ {
+ if (txn_row->new) {
+ txn_row->new->txn_row = NULL;
+ }
+ cb(txn_row);
+ free(txn_row);
+ }
+
+ hmap_destroy(&txn_table->txn_rows);
+ free(txn_table);
+ }
+ hmap_destroy(&txn->txn_tables);
+ free(txn);
+}
+
+static void
+ovsdb_txn_row_abort(struct ovsdb_txn_row *txn_row)
+{
+ struct ovsdb_row *old = txn_row->old;
+ struct ovsdb_row *new = txn_row->new;
+
+ if (!old) {
+ hmap_remove(&new->table->rows, &new->hmap_node);
+ } else if (!new) {
+ hmap_insert(&old->table->rows, &old->hmap_node, ovsdb_row_hash(old));
+ } else {
+ hmap_replace(&new->table->rows, &new->hmap_node, &old->hmap_node);
+ }
+ ovsdb_row_destroy(new);
+}
+
+void
+ovsdb_txn_abort(struct ovsdb_txn *txn)
+{
+ ovsdb_txn_destroy(txn, ovsdb_txn_row_abort);
+}
+
+static void
+ovsdb_txn_row_commit(struct ovsdb_txn_row *txn_row)
+{
+ ovsdb_row_destroy(txn_row->old);
+}
+
+void
+ovsdb_txn_commit(struct ovsdb_txn *txn)
+{
+ txn->db->run_triggers = true;
+ ovsdb_txn_destroy(txn, ovsdb_txn_row_commit);
+}
+
+static void
+put_json_column(struct json *object, const struct ovsdb_row *row,
+ const struct ovsdb_column *column)
+{
+ json_object_put(object, column->name,
+ ovsdb_datum_to_json(&row->fields[column->index],
+ &column->type));
+}
+
+static struct json *
+ovsdb_txn_row_to_json(const struct ovsdb_txn_row *txn_row)
+{
+ const struct ovsdb_row *old = txn_row->old;
+ const struct ovsdb_row *new = txn_row->new;
+ struct shash_node *node;
+ struct json *json;
+
+ if (!new) {
+ return json_null_create();
+ }
+
+ json = NULL;
+ SHASH_FOR_EACH (node, &new->table->schema->columns) {
+ struct ovsdb_column *column = node->data;
+ unsigned int index = column->index;
+
+ if (index != OVSDB_COL_UUID && column->persistent
+ && (!old || !ovsdb_datum_equals(&old->fields[index],
+ &new->fields[index],
+ &column->type)))
+ {
+ if (!json) {
+ json = json_object_create();
+ }
+ put_json_column(json, new, column);
+ }
+ }
+ return json;
+}
+
+static struct json *
+ovsdb_txn_table_to_json(const struct ovsdb_txn_table *txn_table)
+{
+ struct ovsdb_txn_row *txn_row;
+ struct json *txn_table_json;
+
+ txn_table_json = NULL;
+ HMAP_FOR_EACH (txn_row, struct ovsdb_txn_row, hmap_node,
+ &txn_table->txn_rows) {
+ struct json *txn_row_json = ovsdb_txn_row_to_json(txn_row);
+ if (txn_row_json) {
+ char uuid[UUID_LEN + 1];
+
+ if (!txn_table_json) {
+ txn_table_json = json_object_create();
+ }
+
+ snprintf(uuid, sizeof uuid,
+ UUID_FMT, UUID_ARGS(ovsdb_txn_row_get_uuid(txn_row)));
+ json_object_put(txn_table_json, uuid, txn_row_json);
+ }
+ }
+ return txn_table_json;
+}
+
+struct json *
+ovsdb_txn_to_json(const struct ovsdb_txn *txn)
+{
+ struct ovsdb_txn_table *txn_table;
+ struct json *txn_json;
+
+ txn_json = NULL;
+ HMAP_FOR_EACH (txn_table, struct ovsdb_txn_table, hmap_node,
+ &txn->txn_tables) {
+ struct json *txn_table_json = ovsdb_txn_table_to_json(txn_table);
+ if (!txn_json) {
+ txn_json = json_object_create();
+ }
+ json_object_put(txn_json, txn_table->table->schema->name,
+ txn_table_json);
+ }
+ return txn_json;
+}
+
+static struct ovsdb_error *
+ovsdb_txn_row_from_json(struct ovsdb_txn *txn, struct ovsdb_table *table,
+ const struct uuid *row_uuid, struct json *json)
+{
+ const struct ovsdb_row *row = ovsdb_table_get_row(table, row_uuid);
+ if (json->type == JSON_NULL) {
+ if (!row) {
+ return ovsdb_syntax_error(NULL, NULL, "transaction deletes "
+ "row "UUID_FMT" that does not exist",
+ UUID_ARGS(row_uuid));
+ }
+ ovsdb_txn_row_delete(txn, row);
+ return NULL;
+ } else if (row) {
+ return ovsdb_row_from_json(ovsdb_txn_row_modify(txn, row),
+ json, NULL, NULL);
+ } else {
+ struct ovsdb_error *error;
+ struct ovsdb_row *new;
+
+ new = ovsdb_row_create(table);
+ *ovsdb_row_get_uuid_rw(new) = *row_uuid;
+ error = ovsdb_row_from_json(new, json, NULL, NULL);
+ if (error) {
+ ovsdb_row_destroy(new);
+ }
+
+ ovsdb_txn_row_insert(txn, new);
+
+ return error;
+ }
+}
+
+static struct ovsdb_error *
+ovsdb_txn_table_from_json(struct ovsdb_txn *txn, struct ovsdb_table *table,
+ struct json *json)
+{
+ struct shash_node *node;
+
+ if (json->type != JSON_OBJECT) {
+ return ovsdb_syntax_error(json, NULL, "object expected");
+ }
+
+ SHASH_FOR_EACH (node, json->u.object) {
+ const char *uuid_string = node->name;
+ struct json *txn_row_json = node->data;
+ struct ovsdb_error *error;
+ struct uuid row_uuid;
+
+ if (!uuid_from_string(&row_uuid, uuid_string)) {
+ return ovsdb_syntax_error(json, NULL, "\"%s\" is not a valid UUID",
+ uuid_string);
+ }
+
+ error = ovsdb_txn_row_from_json(txn, table, &row_uuid, txn_row_json);
+ if (error) {
+ return error;
+ }
+ }
+
+ return NULL;
+}
+
+struct ovsdb_error *
+ovsdb_txn_from_json(struct ovsdb *db, const struct json *json,
+ struct ovsdb_txn **txnp)
+{
+ struct ovsdb_error *error;
+ struct shash_node *node;
+ struct ovsdb_txn *txn;
+
+ *txnp = NULL;
+ if (json->type != JSON_OBJECT) {
+ return ovsdb_syntax_error(json, NULL, "object expected");
+ }
+
+ txn = ovsdb_txn_create(db);
+ SHASH_FOR_EACH (node, json->u.object) {
+ const char *table_name = node->name;
+ struct json *txn_table_json = node->data;
+ struct ovsdb_table *table;
+
+ table = shash_find_data(&db->tables, table_name);
+ if (!table) {
+ error = ovsdb_syntax_error(json, "unknown table",
+ "No table named %s.", table_name);
+ goto error;
+ }
+
+ error = ovsdb_txn_table_from_json(txn, table, txn_table_json);
+ if (error) {
+ goto error;
+ }
+ }
+ *txnp = txn;
+ return NULL;
+
+error:
+ ovsdb_txn_abort(txn);
+ return error;
+}
+
+static struct ovsdb_txn_table *
+ovsdb_txn_get_txn_table__(struct ovsdb_txn *txn,
+ const struct ovsdb_table *table,
+ uint32_t hash)
+{
+ struct ovsdb_txn_table *txn_table;
+
+ HMAP_FOR_EACH_IN_BUCKET (txn_table, struct ovsdb_txn_table, hmap_node,
+ hash, &txn->txn_tables) {
+ if (txn_table->table == table) {
+ return txn_table;
+ }
+ }
+
+ return NULL;
+}
+
+static struct ovsdb_txn_table *
+ovsdb_txn_get_txn_table(struct ovsdb_txn *txn, const struct ovsdb_table *table)
+{
+ return ovsdb_txn_get_txn_table__(txn, table, hash_pointer(table, 0));
+}
+
+static struct ovsdb_txn_table *
+ovsdb_txn_create_txn_table(struct ovsdb_txn *txn,
+ struct ovsdb_table *table)
+{
+ uint32_t hash = hash_pointer(table, 0);
+ struct ovsdb_txn_table *txn_table;
+
+ txn_table = ovsdb_txn_get_txn_table__(txn, table, hash);
+ if (!txn_table) {
+ txn_table = xmalloc(sizeof *txn_table);
+ txn_table->table = table;
+ hmap_init(&txn_table->txn_rows);
+ hmap_insert(&txn->txn_tables, &txn_table->hmap_node, hash);
+ }
+ return txn_table;
+}
+
+static struct ovsdb_txn_row *
+ovsdb_txn_row_create(struct ovsdb_txn_table *txn_table,
+ const struct ovsdb_row *old, struct ovsdb_row *new)
+{
+ uint32_t hash = ovsdb_row_hash(old ? old : new);
+ struct ovsdb_txn_row *txn_row;
+
+ txn_row = xmalloc(sizeof *txn_row);
+ txn_row->old = (struct ovsdb_row *) old;
+ txn_row->new = new;
+ hmap_insert(&txn_table->txn_rows, &txn_row->hmap_node, hash);
+
+ return txn_row;
+}
+
+struct ovsdb_row *
+ovsdb_txn_row_modify(struct ovsdb_txn *txn, const struct ovsdb_row *ro_row_)
+{
+ struct ovsdb_row *ro_row = (struct ovsdb_row *) ro_row_;
+
+ if (ro_row->txn_row) {
+ assert(ro_row == ro_row->txn_row->new);
+ return ro_row;
+ } else {
+ struct ovsdb_table *table = ro_row->table;
+ struct ovsdb_txn_table *txn_table;
+ struct ovsdb_row *rw_row;
+
+ txn_table = ovsdb_txn_create_txn_table(txn, table);
+ rw_row = ovsdb_row_clone(ro_row);
+ uuid_generate(ovsdb_row_get_version_rw(rw_row));
+ rw_row->txn_row = ovsdb_txn_row_create(txn_table, ro_row, rw_row);
+ hmap_replace(&table->rows, &ro_row->hmap_node, &rw_row->hmap_node);
+
+ return rw_row;
+ }
+}
+
+void
+ovsdb_txn_row_insert(struct ovsdb_txn *txn, struct ovsdb_row *row)
+{
+ uint32_t hash = ovsdb_row_hash(row);
+ struct ovsdb_table *table = row->table;
+ struct ovsdb_txn_table *txn_table;
+
+ uuid_generate(ovsdb_row_get_version_rw(row));
+
+ txn_table = ovsdb_txn_create_txn_table(txn, table);
+ row->txn_row = ovsdb_txn_row_create(txn_table, NULL, row);
+ hmap_insert(&table->rows, &row->hmap_node, hash);
+}
+
+/* 'row' must be assumed destroyed upon return; the caller must not reference
+ * it again. */
+void
+ovsdb_txn_row_delete(struct ovsdb_txn *txn, const struct ovsdb_row *row_)
+{
+ struct ovsdb_row *row = (struct ovsdb_row *) row_;
+ struct ovsdb_table *table = row->table;
+ struct ovsdb_txn_row *txn_row = row->txn_row;
+ struct ovsdb_txn_table *txn_table;
+
+ hmap_remove(&table->rows, &row->hmap_node);
+
+ if (!txn_row) {
+ txn_table = ovsdb_txn_create_txn_table(txn, table);
+ row->txn_row = ovsdb_txn_row_create(txn_table, row, NULL);
+ } else {
+ assert(txn_row->new == row);
+ if (txn_row->old) {
+ txn_row->new = NULL;
+ } else {
+ txn_table = ovsdb_txn_get_txn_table(txn, table);
+ hmap_remove(&txn_table->txn_rows, &txn_row->hmap_node);
+ }
+ ovsdb_row_destroy(row);
+ }
+}
diff --git a/ovsdb/transaction.h b/ovsdb/transaction.h
new file mode 100644
index 00000000..293eaf4f
--- /dev/null
+++ b/ovsdb/transaction.h
@@ -0,0 +1,41 @@
+/* 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_TRANSACTION_H
+#define OVSDB_TRANSACTION_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+
+struct ovsdb;
+struct ovsdb_table;
+struct uuid;
+
+struct ovsdb_txn *ovsdb_txn_create(struct ovsdb *);
+void ovsdb_txn_abort(struct ovsdb_txn *);
+void ovsdb_txn_commit(struct ovsdb_txn *);
+
+struct json *ovsdb_txn_to_json(const struct ovsdb_txn *);
+struct ovsdb_error *ovsdb_txn_from_json(struct ovsdb *, const struct json *,
+ struct ovsdb_txn **)
+ WARN_UNUSED_RESULT;
+
+struct ovsdb_row *ovsdb_txn_row_modify(struct ovsdb_txn *,
+ const struct ovsdb_row *);
+
+void ovsdb_txn_row_insert(struct ovsdb_txn *, struct ovsdb_row *);
+void ovsdb_txn_row_delete(struct ovsdb_txn *, const struct ovsdb_row *);
+
+#endif /* ovsdb/transaction.h */
diff --git a/ovsdb/trigger.c b/ovsdb/trigger.c
new file mode 100644
index 00000000..1ecfdcac
--- /dev/null
+++ b/ovsdb/trigger.c
@@ -0,0 +1,129 @@
+/* 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 "trigger.h"
+
+#include <assert.h>
+#include <limits.h>
+
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb.h"
+#include "poll-loop.h"
+
+static bool ovsdb_trigger_try(struct ovsdb *db, struct ovsdb_trigger *,
+ long long int now);
+static void ovsdb_trigger_complete(struct ovsdb_trigger *);
+
+void
+ovsdb_trigger_init(struct ovsdb *db, struct ovsdb_trigger *trigger,
+ struct json *request, struct list *completion,
+ long long int now)
+{
+ list_push_back(&db->triggers, &trigger->node);
+ trigger->completion = completion;
+ trigger->request = request;
+ trigger->result = NULL;
+ trigger->created = now;
+ trigger->timeout_msec = LLONG_MAX;
+ ovsdb_trigger_try(db, trigger, now);
+}
+
+void
+ovsdb_trigger_destroy(struct ovsdb_trigger *trigger)
+{
+ list_remove(&trigger->node);
+ json_destroy(trigger->request);
+ json_destroy(trigger->result);
+}
+
+bool
+ovsdb_trigger_is_complete(const struct ovsdb_trigger *trigger)
+{
+ return trigger->result != NULL;
+}
+
+struct json *
+ovsdb_trigger_steal_result(struct ovsdb_trigger *trigger)
+{
+ struct json *result = trigger->result;
+ trigger->result = NULL;
+ return result;
+}
+
+void
+ovsdb_trigger_run(struct ovsdb *db, long long int now)
+{
+ struct ovsdb_trigger *t, *next;
+ bool run_triggers;
+
+ run_triggers = db->run_triggers;
+ db->run_triggers = false;
+ LIST_FOR_EACH_SAFE (t, next, struct ovsdb_trigger, node, &db->triggers) {
+ if (run_triggers || now - t->created >= t->timeout_msec) {
+ ovsdb_trigger_try(db, t, now);
+ }
+ }
+}
+
+void
+ovsdb_trigger_wait(struct ovsdb *db, long long int now)
+{
+ if (db->run_triggers) {
+ poll_immediate_wake();
+ } else {
+ long long int deadline = LLONG_MAX;
+ struct ovsdb_trigger *t;
+
+ LIST_FOR_EACH (t, struct ovsdb_trigger, node, &db->triggers) {
+ if (t->created < LLONG_MAX - t->timeout_msec) {
+ long long int t_deadline = t->created + t->timeout_msec;
+ if (deadline > t_deadline) {
+ deadline = t_deadline;
+ if (now >= deadline) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (deadline < LLONG_MAX) {
+ poll_timer_wait(MIN(deadline - now, INT_MAX));
+ }
+ }
+}
+
+static bool
+ovsdb_trigger_try(struct ovsdb *db, struct ovsdb_trigger *t, long long int now)
+{
+ t->result = ovsdb_execute(db, t->request, now - t->created,
+ &t->timeout_msec);
+ if (t->result) {
+ ovsdb_trigger_complete(t);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static void
+ovsdb_trigger_complete(struct ovsdb_trigger *t)
+{
+ assert(t->result != NULL);
+ list_remove(&t->node);
+ list_push_back(t->completion, &t->node);
+}
diff --git a/ovsdb/trigger.h b/ovsdb/trigger.h
new file mode 100644
index 00000000..521b150b
--- /dev/null
+++ b/ovsdb/trigger.h
@@ -0,0 +1,44 @@
+/* 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_TRIGGER_H
+#define OVSDB_TRIGGER_H 1
+
+#include "list.h"
+
+struct ovsdb;
+
+struct ovsdb_trigger {
+ struct list node; /* !result: in struct ovsdb "triggers" list;
+ * result: in completion list. */
+ struct list *completion; /* Completion list. */
+ struct json *request; /* Database request. */
+ struct json *result; /* Result (null if none yet). */
+ long long int created; /* Time created. */
+ long long int timeout_msec; /* Max wait duration. */
+};
+
+void ovsdb_trigger_init(struct ovsdb *, struct ovsdb_trigger *,
+ struct json *request, struct list *completion,
+ long long int now);
+void ovsdb_trigger_destroy(struct ovsdb_trigger *);
+
+bool ovsdb_trigger_is_complete(const struct ovsdb_trigger *);
+struct json *ovsdb_trigger_steal_result(struct ovsdb_trigger *);
+
+void ovsdb_trigger_run(struct ovsdb *, long long int now);
+void ovsdb_trigger_wait(struct ovsdb *, long long int now);
+
+#endif /* ovsdb/trigger.h */
diff --git a/tests/automake.mk b/tests/automake.mk
index 62845112..e8a429fa 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -15,6 +15,18 @@ TESTSUITE_AT = \
tests/jsonrpc.at \
tests/timeval.at \
tests/lockfile.at \
+ tests/ovsdb.at \
+ tests/ovsdb-file.at \
+ tests/ovsdb-types.at \
+ tests/ovsdb-data.at \
+ tests/ovsdb-column.at \
+ tests/ovsdb-table.at \
+ tests/ovsdb-row.at \
+ tests/ovsdb-condition.at \
+ tests/ovsdb-query.at \
+ tests/ovsdb-transaction.at \
+ tests/ovsdb-execution.at \
+ tests/ovsdb-trigger.at \
tests/stp.at \
tests/ovs-vsctl.at \
tests/lcov-post.at
@@ -89,6 +101,11 @@ noinst_PROGRAMS += tests/test-lockfile
tests_test_lockfile_SOURCES = tests/test-lockfile.c
tests_test_lockfile_LDADD = lib/libopenvswitch.a
+noinst_PROGRAMS += tests/test-ovsdb
+tests_test_ovsdb_SOURCES = tests/test-ovsdb.c
+tests_test_ovsdb_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a
+EXTRA_DIST += tests/uuidfilt.pl
+
noinst_PROGRAMS += tests/test-sha1
tests_test_sha1_SOURCES = tests/test-sha1.c
tests_test_sha1_LDADD = lib/libopenvswitch.a
diff --git a/tests/library.at b/tests/library.at
index c48828e9..ffcd4b83 100644
--- a/tests/library.at
+++ b/tests/library.at
@@ -11,6 +11,7 @@ OVS_CHECK_LCOV([test-csum], [0], [ignore])
AT_CLEANUP
AT_SETUP([test flow classifier])
+AT_KEYWORDS([slow])
OVS_CHECK_LCOV([test-classifier], [0], [ignore])
AT_CLEANUP
diff --git a/tests/ovsdb-column.at b/tests/ovsdb-column.at
new file mode 100644
index 00000000..03cd8dc6
--- /dev/null
+++ b/tests/ovsdb-column.at
@@ -0,0 +1,18 @@
+AT_BANNER([OVSDB -- columns])
+
+OVSDB_CHECK_POSITIVE([ordinary column],
+ [[parse-column mycol '{"type": "integer"}']],
+ [[{"type":"integer"}]])
+
+OVSDB_CHECK_POSITIVE([immutable column],
+ [[parse-column mycol '{"type": "real", "mutable": false}']],
+ [[{"mutable":false,"type":"real"}]])
+
+OVSDB_CHECK_POSITIVE([ephemeral column],
+ [[parse-column mycol '{"type": "uuid", "ephemeral": true}']],
+ [[{"ephemeral":true,"type":"uuid"}]])
+
+OVSDB_CHECK_POSITIVE([column with comment],
+ [[parse-column mycol '{"type": "boolean",
+ "comment": "extra information about this column"}']],
+ [[{"comment":"extra information about this column","type":"boolean"}]])
diff --git a/tests/ovsdb-condition.at b/tests/ovsdb-condition.at
new file mode 100644
index 00000000..3715b6d4
--- /dev/null
+++ b/tests/ovsdb-condition.at
@@ -0,0 +1,558 @@
+AT_BANNER([OVSDB -- conditions])
+
+OVSDB_CHECK_POSITIVE([null condition],
+ [[parse-conditions \
+ '{"columns": {"name": {"type": "string"}}}' \
+ '[]']],
+ [[[]]])
+
+OVSDB_CHECK_POSITIVE([conditions on scalars],
+ [[parse-conditions \
+ '{"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]]' \
+ '[["i", ">=", 5]]' \
+ '[["i", "includes", 6]]' \
+ '[["i", "excludes", 7]]' \
+ '[["r", "==", 0.5]]' \
+ '[["r", "!=", 1.5]]' \
+ '[["r", "<", 2.5]]' \
+ '[["r", "<=", 3.5]]' \
+ '[["r", ">", 4.5]]' \
+ '[["r", ">=", 5.5]]' \
+ '[["r", "includes", 6.5]]' \
+ '[["r", "excludes", 7.5]]' \
+ '[["b", "==", true]]' \
+ '[["b", "!=", false]]' \
+ '[["b", "includes", false]]' \
+ '[["b", "excludes", true]]' \
+ '[["s", "==", "a"]]' \
+ '[["s", "!=", "b"]]' \
+ '[["s", "includes", "c"]]' \
+ '[["s", "excludes", "d"]]' \
+ '[["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]' \
+ '[["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+ '[["u", "includes", ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]' \
+ '[["u", "excludes", ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]]]']],
+ [[[["i","==",0]]
+[["i","!=",1]]
+[["i","<",2]]
+[["i","<=",3]]
+[["i",">",4]]
+[["i",">=",5]]
+[["i","includes",6]]
+[["i","excludes",7]]
+[["r","==",0.5]]
+[["r","!=",1.5]]
+[["r","<",2.5]]
+[["r","<=",3.5]]
+[["r",">",4.5]]
+[["r",">=",5.5]]
+[["r","includes",6.5]]
+[["r","excludes",7.5]]
+[["b","==",true]]
+[["b","!=",false]]
+[["b","includes",false]]
+[["b","excludes",true]]
+[["s","==","a"]]
+[["s","!=","b"]]
+[["s","includes","c"]]
+[["s","excludes","d"]]
+[["u","==",["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]
+[["u","!=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]]
+[["u","includes",["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]
+[["u","excludes",["uuid","62315898-64e0-40b9-b26f-ff74225303e6"]]]]],
+ [condition])
+
+AT_SETUP([disallowed conditions on scalars])
+AT_KEYWORDS([ovsdb negative condition])
+OVS_CHECK_LCOV([[test-ovsdb parse-conditions \
+ '{"columns":
+ {"i": {"type": "integer"},
+ "r": {"type": "real"},
+ "b": {"type": "boolean"},
+ "s": {"type": "string"},
+ "u": {"type": "uuid"}}}' \
+ '[["b", ">", true]]' \
+ '[["b", ">=", false]]' \
+ '[["b", "<", false]]' \
+ '[["b", "<=", false]]' \
+ '[["s", ">", "a"]]' \
+ '[["s", ">=", "b"]]' \
+ '[["s", "<", "c"]]' \
+ '[["s", "<=", "d"]]' \
+ '[["u", ">", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]' \
+ '[["u", ">=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+ '[["u", "<", ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]' \
+ '[["u", "<=", ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]]]']],
+ [1], [],
+ [[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","<",false]": 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 "["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 "["u",">",["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]": 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","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]": syntax error: Type mismatch: "<" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","<=",["uuid","62315898-64e0-40b9-b26f-ff74225303e6"]]": syntax error: Type mismatch: "<=" operator may not be applied to column u of type uuid.
+]])
+AT_CLEANUP
+
+OVSDB_CHECK_POSITIVE([conditions on sets],
+ [[parse-conditions \
+ '{"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", "==", ["set", []]]]' \
+ '[["i", "!=", ["set", [1]]]]' \
+ '[["i", "includes", ["set", [1, 2]]]]' \
+ '[["i", "excludes", ["set", [1, 2, 3]]]]' \
+ '[["r", "==", ["set", []]]]' \
+ '[["r", "!=", ["set", [1.5]]]]' \
+ '[["r", "includes", ["set", [1.5, 2.5]]]]' \
+ '[["r", "excludes", ["set", [1.5, 2.5, 3.5]]]]' \
+ '[["b", "==", ["set", [true]]]]' \
+ '[["b", "!=", ["set", [false]]]]' \
+ '[["b", "includes", ["set", [false]]]]' \
+ '[["b", "excludes", ["set", [true, false]]]]' \
+ '[["s", "==", ["set", ["a"]]]]' \
+ '[["s", "!=", ["set", ["a", "b"]]]]' \
+ '[["s", "includes", ["set", ["c"]]]]' \
+ '[["s", "excludes", ["set", ["c", "d"]]]]' \
+ '[["u", "==",
+ ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]' \
+ '[["u", "==",
+ ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"],
+ ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]]]' \
+ '[["u", "includes",
+ ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"],
+ ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"],
+ ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]]' \
+ '[["u", "excludes",
+ ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"],
+ ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"],
+ ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"],
+ ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]]]]]' \
+]],
+ [[[["i","==",["set",[]]]]
+[["i","!=",["set",[1]]]]
+[["i","includes",["set",[1,2]]]]
+[["i","excludes",["set",[1,2,3]]]]
+[["r","==",["set",[]]]]
+[["r","!=",["set",[1.5]]]]
+[["r","includes",["set",[1.5,2.5]]]]
+[["r","excludes",["set",[1.5,2.5,3.5]]]]
+[["b","==",["set",[true]]]]
+[["b","!=",["set",[false]]]]
+[["b","includes",["set",[false]]]]
+[["b","excludes",["set",[false,true]]]]
+[["s","==",["set",["a"]]]]
+[["s","!=",["set",["a","b"]]]]
+[["s","includes",["set",["c"]]]]
+[["s","excludes",["set",["c","d"]]]]
+[["u","==",["set",[["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]
+[["u","==",["set",[["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]
+[["u","includes",["set",[["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]
+[["u","excludes",["set",[["uuid","62315898-64e0-40b9-b26f-ff74225303e6"],["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]]],
+ [condition])
+
+OVSDB_CHECK_POSITIVE([condition sorting],
+ [[parse-conditions \
+ '{"columns": {"i": {"type": "integer"}}}' \
+ '[["i", "excludes", 7],
+ ["i", "!=", 8],
+ ["i", "==", 1],
+ ["i", "includes", 2],
+ ["i", "<=", 3],
+ ["i", "<", 4],
+ ["i", ">", 6],
+ ["i", ">=", 5],
+ ["_uuid", "==", ["uuid", "d50e85c6-8ae7-4b16-b69e-4395928bd9be"]]]']],
+ [[[["_uuid","==",["uuid","d50e85c6-8ae7-4b16-b69e-4395928bd9be"]],["i","==",1],["i","includes",2],["i","<=",3],["i","<",4],["i",">=",5],["i",">",6],["i","excludes",7],["i","!=",8]]]])
+
+OVSDB_CHECK_POSITIVE([evaluating null condition],
+ [[evaluate-conditions \
+ '{"columns": {"i": {"type": "integer"}}}' \
+ '[[]]' \
+ '[{"i": 0},
+ {"i": 1},
+ {"i": 2}']]],
+ [condition 0: TTT])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on integers],
+ [[evaluate-conditions \
+ '{"columns": {"i": {"type": "integer"}}}' \
+ '[[["i", "<", 1]],
+ [["i", "<=", 1]],
+ [["i", "==", 1]],
+ [["i", "!=", 1]],
+ [["i", ">=", 1]],
+ [["i", ">", 1]],
+ [["i", "includes", 1]],
+ [["i", "excludes", 1]]]' \
+ '[{"i": 0},
+ {"i": 1},
+ {"i": 2}']]],
+ [condition 0: T--
+condition 1: TT-
+condition 2: -T-
+condition 3: T-T
+condition 4: -TT
+condition 5: --T
+condition 6: -T-
+condition 7: T-T], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on reals],
+ [[evaluate-conditions \
+ '{"columns": {"r": {"type": "real"}}}' \
+ '[[["r", "<", 5.0]],
+ [["r", "<=", 5.0]],
+ [["r", "==", 5.0]],
+ [["r", "!=", 5.0]],
+ [["r", ">=", 5.0]],
+ [["r", ">", 5.0]],
+ [["r", "includes", 5.0]],
+ [["r", "excludes", 5.0]]]' \
+ '[{"r": 0},
+ {"r": 5.0},
+ {"r": 5.1}']]],
+ [condition 0: T--
+condition 1: TT-
+condition 2: -T-
+condition 3: T-T
+condition 4: -TT
+condition 5: --T
+condition 6: -T-
+condition 7: T-T], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on booleans],
+ [[evaluate-conditions \
+ '{"columns": {"b": {"type": "boolean"}}}' \
+ '[[["b", "==", true]],
+ [["b", "!=", true]],
+ [["b", "includes", true]],
+ [["b", "excludes", true]],
+ [["b", "==", false]],
+ [["b", "!=", false]],
+ [["b", "includes", false]],
+ [["b", "excludes", false]]]' \
+ '[{"b": true},
+ {"b": false}']]],
+ [condition 0: T-
+condition 1: -T
+condition 2: T-
+condition 3: -T
+condition 4: -T
+condition 5: T-
+condition 6: -T
+condition 7: T-], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on strings],
+ [[evaluate-conditions \
+ '{"columns": {"s": {"type": "string"}}}' \
+ '[[["s", "==", ""]],
+ [["s", "!=", ""]],
+ [["s", "includes", ""]],
+ [["s", "excludes", ""]],
+ [["s", "==", "foo"]],
+ [["s", "!=", "foo"]],
+ [["s", "includes", "foo"]],
+ [["s", "excludes", "foo"]]]' \
+ '[{"s": ""},
+ {"s": "foo"},
+ {"s": "xxx"}']]],
+ [condition 0: T--
+condition 1: -TT
+condition 2: T--
+condition 3: -TT
+condition 4: -T-
+condition 5: T-T
+condition 6: -T-
+condition 7: T-T], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on UUIDs],
+ [[evaluate-conditions \
+ '{"columns": {"u": {"type": "uuid"}}}' \
+ '[[["u", "==", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]],
+ [["u", "!=", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]],
+ [["u", "includes", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]],
+ [["u", "excludes", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]],
+ [["u", "==", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]],
+ [["u", "!=", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]],
+ [["u", "includes", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]],
+ [["u", "excludes", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]]]' \
+ '[{"u": ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]},
+ {"u": ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]},
+ {"u": ["uuid", "00000000-0000-0000-0000-000000000000"]}']]],
+ [condition 0: T--
+condition 1: -TT
+condition 2: T--
+condition 3: -TT
+condition 4: -T-
+condition 5: T-T
+condition 6: -T-
+condition 7: T-T], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on sets],
+ [[evaluate-conditions \
+ '{"columns": {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}}}}' \
+ '[[["i", "==", ["set", []]]],
+ [["i", "==", ["set", [0]]]],
+ [["i", "==", ["set", [1]]]],
+ [["i", "==", ["set", [0, 1]]]],
+ [["i", "==", ["set", [2]]]],
+ [["i", "==", ["set", [2, 0]]]],
+ [["i", "==", ["set", [2, 1]]]],
+ [["i", "==", ["set", [2, 1, 0]]]],
+ [["i", "!=", ["set", []]]],
+ [["i", "!=", ["set", [0]]]],
+ [["i", "!=", ["set", [1]]]],
+ [["i", "!=", ["set", [0, 1]]]],
+ [["i", "!=", ["set", [2]]]],
+ [["i", "!=", ["set", [2, 0]]]],
+ [["i", "!=", ["set", [2, 1]]]],
+ [["i", "!=", ["set", [2, 1, 0]]]],
+ [["i", "includes", ["set", []]]],
+ [["i", "includes", ["set", [0]]]],
+ [["i", "includes", ["set", [1]]]],
+ [["i", "includes", ["set", [0, 1]]]],
+ [["i", "includes", ["set", [2]]]],
+ [["i", "includes", ["set", [2, 0]]]],
+ [["i", "includes", ["set", [2, 1]]]],
+ [["i", "includes", ["set", [2, 1, 0]]]],
+ [["i", "excludes", ["set", []]]],
+ [["i", "excludes", ["set", [0]]]],
+ [["i", "excludes", ["set", [1]]]],
+ [["i", "excludes", ["set", [0, 1]]]],
+ [["i", "excludes", ["set", [2]]]],
+ [["i", "excludes", ["set", [2, 0]]]],
+ [["i", "excludes", ["set", [2, 1]]]],
+ [["i", "excludes", ["set", [2, 1, 0]]]]]' \
+ '[{"i": ["set", []]},
+ {"i": ["set", [0]]},
+ {"i": ["set", [1]]},
+ {"i": ["set", [0, 1]]},
+ {"i": ["set", [2]]},
+ {"i": ["set", [2, 0]]},
+ {"i": ["set", [2, 1]]},
+ {"i": ["set", [2, 1, 0]]}]']],
+ [dnl
+condition 0: T---- ---
+condition 1: -T--- ---
+condition 2: --T-- ---
+condition 3: ---T- ---
+condition 4: ----T ---
+condition 5: ----- T--
+condition 6: ----- -T-
+condition 7: ----- --T
+condition 8: -TTTT TTT
+condition 9: T-TTT TTT
+condition 10: TT-TT TTT
+condition 11: TTT-T TTT
+condition 12: TTTT- TTT
+condition 13: TTTTT -TT
+condition 14: TTTTT T-T
+condition 15: TTTTT TT-
+condition 16: TTTTT TTT
+condition 17: -T-T- T-T
+condition 18: --TT- -TT
+condition 19: ---T- --T
+condition 20: ----T TTT
+condition 21: ----- T-T
+condition 22: ----- -TT
+condition 23: ----- --T
+condition 24: TTTTT TTT
+condition 25: T-T-T -T-
+condition 26: TT--T T--
+condition 27: T---T ---
+condition 28: TTTT- ---
+condition 29: T-T-- ---
+condition 30: TT--- ---
+condition 31: T---- ---], [condition])
+
+# This is the same as the "set" test except that it adds values,
+# all of which always match.
+OVSDB_CHECK_POSITIVE([evaluating conditions on maps (1)],
+ [[evaluate-conditions \
+ '{"columns": {"i": {"type": {"key": "integer",
+ "value": "boolean",
+ "min": 0,
+ "max": "unlimited"}}}}' \
+ '[[["i", "==", ["map", []]]],
+ [["i", "==", ["map", [[0, true]]]]],
+ [["i", "==", ["map", [[1, false]]]]],
+ [["i", "==", ["map", [[0, true], [1, false]]]]],
+ [["i", "==", ["map", [[2, true]]]]],
+ [["i", "==", ["map", [[2, true], [0, true]]]]],
+ [["i", "==", ["map", [[2, true], [1, false]]]]],
+ [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]],
+ [["i", "!=", ["map", []]]],
+ [["i", "!=", ["map", [[0, true]]]]],
+ [["i", "!=", ["map", [[1, false]]]]],
+ [["i", "!=", ["map", [[0, true], [1, false]]]]],
+ [["i", "!=", ["map", [[2, true]]]]],
+ [["i", "!=", ["map", [[2, true], [0, true]]]]],
+ [["i", "!=", ["map", [[2, true], [1, false]]]]],
+ [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]],
+ [["i", "includes", ["map", []]]],
+ [["i", "includes", ["map", [[0, true]]]]],
+ [["i", "includes", ["map", [[1, false]]]]],
+ [["i", "includes", ["map", [[0, true], [1, false]]]]],
+ [["i", "includes", ["map", [[2, true]]]]],
+ [["i", "includes", ["map", [[2, true], [0, true]]]]],
+ [["i", "includes", ["map", [[2, true], [1, false]]]]],
+ [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]],
+ [["i", "excludes", ["map", []]]],
+ [["i", "excludes", ["map", [[0, true]]]]],
+ [["i", "excludes", ["map", [[1, false]]]]],
+ [["i", "excludes", ["map", [[0, true], [1, false]]]]],
+ [["i", "excludes", ["map", [[2, true]]]]],
+ [["i", "excludes", ["map", [[2, true], [0, true]]]]],
+ [["i", "excludes", ["map", [[2, true], [1, false]]]]],
+ [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]' \
+ '[{"i": ["map", []]},
+ {"i": ["map", [[0, true]]]},
+ {"i": ["map", [[1, false]]]},
+ {"i": ["map", [[0, true], [1, false]]]},
+ {"i": ["map", [[2, true]]]},
+ {"i": ["map", [[2, true], [0, true]]]},
+ {"i": ["map", [[2, true], [1, false]]]},
+ {"i": ["map", [[2, true], [1, false], [0, true]]]}]']],
+ [dnl
+condition 0: T---- ---
+condition 1: -T--- ---
+condition 2: --T-- ---
+condition 3: ---T- ---
+condition 4: ----T ---
+condition 5: ----- T--
+condition 6: ----- -T-
+condition 7: ----- --T
+condition 8: -TTTT TTT
+condition 9: T-TTT TTT
+condition 10: TT-TT TTT
+condition 11: TTT-T TTT
+condition 12: TTTT- TTT
+condition 13: TTTTT -TT
+condition 14: TTTTT T-T
+condition 15: TTTTT TT-
+condition 16: TTTTT TTT
+condition 17: -T-T- T-T
+condition 18: --TT- -TT
+condition 19: ---T- --T
+condition 20: ----T TTT
+condition 21: ----- T-T
+condition 22: ----- -TT
+condition 23: ----- --T
+condition 24: TTTTT TTT
+condition 25: T-T-T -T-
+condition 26: TT--T T--
+condition 27: T---T ---
+condition 28: TTTT- ---
+condition 29: T-T-- ---
+condition 30: TT--- ---
+condition 31: T---- ---], [condition])
+
+# This is the same as the "set" test except that it adds values,
+# and those values don't always match.
+OVSDB_CHECK_POSITIVE([evaluating conditions on maps (2)],
+ [[evaluate-conditions \
+ '{"columns": {"i": {"type": {"key": "integer",
+ "value": "boolean",
+ "min": 0,
+ "max": "unlimited"}}}}' \
+ '[[["i", "==", ["map", []]]],
+ [["i", "==", ["map", [[0, true]]]]],
+ [["i", "==", ["map", [[1, false]]]]],
+ [["i", "==", ["map", [[0, true], [1, false]]]]],
+ [["i", "==", ["map", [[2, true]]]]],
+ [["i", "==", ["map", [[2, true], [0, true]]]]],
+ [["i", "==", ["map", [[2, true], [1, false]]]]],
+ [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]],
+ [["i", "!=", ["map", []]]],
+ [["i", "!=", ["map", [[0, true]]]]],
+ [["i", "!=", ["map", [[1, false]]]]],
+ [["i", "!=", ["map", [[0, true], [1, false]]]]],
+ [["i", "!=", ["map", [[2, true]]]]],
+ [["i", "!=", ["map", [[2, true], [0, true]]]]],
+ [["i", "!=", ["map", [[2, true], [1, false]]]]],
+ [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]],
+ [["i", "includes", ["map", []]]],
+ [["i", "includes", ["map", [[0, true]]]]],
+ [["i", "includes", ["map", [[1, false]]]]],
+ [["i", "includes", ["map", [[0, true], [1, false]]]]],
+ [["i", "includes", ["map", [[2, true]]]]],
+ [["i", "includes", ["map", [[2, true], [0, true]]]]],
+ [["i", "includes", ["map", [[2, true], [1, false]]]]],
+ [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]],
+ [["i", "excludes", ["map", []]]],
+ [["i", "excludes", ["map", [[0, true]]]]],
+ [["i", "excludes", ["map", [[1, false]]]]],
+ [["i", "excludes", ["map", [[0, true], [1, false]]]]],
+ [["i", "excludes", ["map", [[2, true]]]]],
+ [["i", "excludes", ["map", [[2, true], [0, true]]]]],
+ [["i", "excludes", ["map", [[2, true], [1, false]]]]],
+ [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]' \
+ '[{"i": ["map", []]},
+ {"i": ["map", [[0, true]]]},
+ {"i": ["map", [[0, false]]]},
+ {"i": ["map", [[1, false]]]},
+ {"i": ["map", [[1, true]]]},
+
+ {"i": ["map", [[0, true], [1, false]]]},
+ {"i": ["map", [[0, true], [1, true]]]},
+ {"i": ["map", [[2, true]]]},
+ {"i": ["map", [[2, false]]]},
+ {"i": ["map", [[2, true], [0, true]]]},
+
+ {"i": ["map", [[2, false], [0, true]]]},
+ {"i": ["map", [[2, true], [1, false]]]},
+ {"i": ["map", [[2, true], [1, true]]]},
+ {"i": ["map", [[2, true], [1, false], [0, true]]]},
+ {"i": ["map", [[2, true], [1, false], [0, false]]]}]']],
+ [dnl
+condition 0: T---- ----- -----
+condition 1: -T--- ----- -----
+condition 2: ---T- ----- -----
+condition 3: ----- T---- -----
+condition 4: ----- --T-- -----
+condition 5: ----- ----T -----
+condition 6: ----- ----- -T---
+condition 7: ----- ----- ---T-
+condition 8: -TTTT TTTTT TTTTT
+condition 9: T-TTT TTTTT TTTTT
+condition 10: TTT-T TTTTT TTTTT
+condition 11: TTTTT -TTTT TTTTT
+condition 12: TTTTT TT-TT TTTTT
+condition 13: TTTTT TTTT- TTTTT
+condition 14: TTTTT TTTTT T-TTT
+condition 15: TTTTT TTTTT TTT-T
+condition 16: TTTTT TTTTT TTTTT
+condition 17: -T--- TT--T T--T-
+condition 18: ---T- T---- -T-TT
+condition 19: ----- T---- ---T-
+condition 20: ----- --T-T -TTTT
+condition 21: ----- ----T ---T-
+condition 22: ----- ----- -T-TT
+condition 23: ----- ----- ---T-
+condition 24: TTTTT TTTTT TTTTT
+condition 25: T-TTT --TT- -TT-T
+condition 26: TTT-T -TTTT T-T--
+condition 27: T-T-T --TT- --T--
+condition 28: TTTTT TT-T- T----
+condition 29: T-TTT ---T- -----
+condition 30: TTT-T -T-T- T----
+condition 31: T-T-T ---T- -----], [condition])
diff --git a/tests/ovsdb-data.at b/tests/ovsdb-data.at
new file mode 100644
index 00000000..bedaf700
--- /dev/null
+++ b/tests/ovsdb-data.at
@@ -0,0 +1,259 @@
+AT_BANNER([OVSDB -- atoms])
+
+OVSDB_CHECK_POSITIVE([integer atom],
+ [[parse-atoms '["integer"]' \
+ '[0]' \
+ '[-1]' \
+ '[1e3]' \
+ '[9223372036854775807]' \
+ '[-9223372036854775808]' ]],
+ [0
+-1
+1000
+9223372036854775807
+-9223372036854775808])
+
+OVSDB_CHECK_POSITIVE([real atom],
+ [[parse-atoms '["real"]' \
+ '[0]' \
+ '[0.0]' \
+ '[-0.0]' \
+ '[-1.25]' \
+ '[1e3]' \
+ '[1e37]' \
+ '[0.00390625]' ]],
+ [0
+0
+0
+-1.25
+1000
+1e+37
+0.00390625])
+
+OVSDB_CHECK_POSITIVE([boolean atom],
+ [[parse-atoms '["boolean"]' '[true]' '[false]' ]],
+ [true
+false])
+
+OVSDB_CHECK_POSITIVE([string atom],
+ [[parse-atoms '["string"]' '[""]' '["true"]' '["\"\\\/\b\f\n\r\t"]']],
+ [""
+"true"
+"\"\\/\b\f\n\r\t"])
+
+OVSDB_CHECK_POSITIVE([uuid atom],
+ [[parse-atoms '["uuid"]' '["uuid", "550e8400-e29b-41d4-a716-446655440000"]']],
+ [[["uuid","550e8400-e29b-41d4-a716-446655440000"]]])
+
+OVSDB_CHECK_POSITIVE([integer atom sorting],
+ [[sort-atoms '["integer"]' '[55,0,-1,2,1]']],
+ [[[-1,0,1,2,55]]])
+
+OVSDB_CHECK_POSITIVE([real atom sorting],
+ [[sort-atoms '["real"]' '[1.25,1.23,0.0,-0.0,-1e99]']],
+ [[[-1e+99,0,0,1.23,1.25]]])
+
+OVSDB_CHECK_POSITIVE([boolean atom sorting],
+ [[sort-atoms '["boolean"]' '[true,false,true,false,false]']],
+ [[[false,false,false,true,true]]])
+
+OVSDB_CHECK_POSITIVE([string atom sorting],
+ [[sort-atoms '["string"]' '["abd","abc","\b","xxx"]']],
+ [[["\b","abc","abd","xxx"]]])
+
+OVSDB_CHECK_POSITIVE([uuid atom sorting],
+ [[sort-atoms '["uuid"]' '[
+ ["uuid", "00000000-0000-0000-0000-000000000001"],
+ ["uuid", "00000000-1000-0000-0000-000000000000"],
+ ["uuid", "00000000-0000-1000-0000-000000000000"],
+ ["uuid", "00010000-0000-0000-0000-000000000000"],
+ ["uuid", "00000000-0000-0000-0000-000000000100"],
+ ["uuid", "00000000-0000-0000-0000-000100000000"],
+ ["uuid", "00000000-0000-0010-0000-000000000000"],
+ ["uuid", "00000100-0000-0000-0000-000000000000"],
+ ["uuid", "00000000-0000-0001-0000-000000000000"],
+ ["uuid", "00000000-0000-0000-0000-000001000000"],
+ ["uuid", "01000000-0000-0000-0000-000000000000"],
+ ["uuid", "00000000-0000-0000-0000-000000001000"],
+ ["uuid", "00000000-0000-0000-0000-000010000000"],
+ ["uuid", "00000000-0000-0000-0000-010000000000"],
+ ["uuid", "00000000-0000-0100-0000-000000000000"],
+ ["uuid", "10000000-0000-0000-0000-000000000000"],
+ ["uuid", "00000000-0000-0000-0000-000000000010"],
+ ["uuid", "00000000-0100-0000-0000-000000000000"],
+ ["uuid", "00000000-0000-0000-0100-000000000000"],
+ ["uuid", "00000000-0000-0000-0001-000000000000"],
+ ["uuid", "00000010-0000-0000-0000-000000000000"],
+ ["uuid", "00000000-0000-0000-0010-000000000000"],
+ ["uuid", "00000000-0000-0000-0000-000000010000"],
+ ["uuid", "00000000-0000-0000-1000-000000000000"],
+ ["uuid", "00000000-0000-0000-0000-100000000000"],
+ ["uuid", "00000000-0000-0000-0000-001000000000"],
+ ["uuid", "00000000-0000-0000-0000-000000100000"],
+ ["uuid", "00000000-0000-0000-0000-000000000000"],
+ ["uuid", "00000000-0010-0000-0000-000000000000"],
+ ["uuid", "00100000-0000-0000-0000-000000000000"],
+ ["uuid", "00000000-0001-0000-0000-000000000000"],
+ ["uuid", "00000001-0000-0000-0000-000000000000"],
+ ["uuid", "00001000-0000-0000-0000-000000000000"]]']],
+ [[[["uuid","00000000-0000-0000-0000-000000000000"],["uuid","00000000-0000-0000-0000-000000000001"],["uuid","00000000-0000-0000-0000-000000000010"],["uuid","00000000-0000-0000-0000-000000000100"],["uuid","00000000-0000-0000-0000-000000001000"],["uuid","00000000-0000-0000-0000-000000010000"],["uuid","00000000-0000-0000-0000-000000100000"],["uuid","00000000-0000-0000-0000-000001000000"],["uuid","00000000-0000-0000-0000-000010000000"],["uuid","00000000-0000-0000-0000-000100000000"],["uuid","00000000-0000-0000-0000-001000000000"],["uuid","00000000-0000-0000-0000-010000000000"],["uuid","00000000-0000-0000-0000-100000000000"],["uuid","00000000-0000-0000-0001-000000000000"],["uuid","00000000-0000-0000-0010-000000000000"],["uuid","00000000-0000-0000-0100-000000000000"],["uuid","00000000-0000-0000-1000-000000000000"],["uuid","00000000-0000-0001-0000-000000000000"],["uuid","00000000-0000-0010-0000-000000000000"],["uuid","00000000-0000-0100-0000-000000000000"],["uuid","00000000-0000-1000-0000-000000000000"],["uuid","00000000-0001-0000-0000-000000000000"],["uuid","00000000-0010-0000-0000-000000000000"],["uuid","00000000-0100-0000-0000-000000000000"],["uuid","00000000-1000-0000-0000-000000000000"],["uuid","00000001-0000-0000-0000-000000000000"],["uuid","00000010-0000-0000-0000-000000000000"],["uuid","00000100-0000-0000-0000-000000000000"],["uuid","00001000-0000-0000-0000-000000000000"],["uuid","00010000-0000-0000-0000-000000000000"],["uuid","00100000-0000-0000-0000-000000000000"],["uuid","01000000-0000-0000-0000-000000000000"],["uuid","10000000-0000-0000-0000-000000000000"]]]])
+
+OVSDB_CHECK_NEGATIVE([real not acceptable integer atom],
+ [[parse-atoms '["integer"]' '[0.5]' ]],
+ [expected integer])
+
+OVSDB_CHECK_NEGATIVE([string "true" not acceptable boolean atom],
+ [[parse-atoms '["boolean"]' '["true"]' ]],
+ [expected boolean])
+
+OVSDB_CHECK_NEGATIVE([integer not acceptable string atom],
+ [[parse-atoms '["string"]' '[1]']],
+ [expected string])
+
+OVSDB_CHECK_NEGATIVE([uuid atom must be expressed as array],
+ [[parse-atoms '["uuid"]' '["550e8400-e29b-41d4-a716-446655440000"]']],
+ [[expected ["uuid", <string>]]])
+
+AT_BANNER([OSVDB -- simple data])
+
+OVSDB_CHECK_POSITIVE([integer datum],
+ [[parse-data '["integer"]' '[0]' '[1]' '[-1]']],
+ [0
+1
+-1])
+
+OVSDB_CHECK_POSITIVE([real datum],
+ [[parse-data '["real"]' '[0]' '[1.0]' '[-1.25]']],
+ [0
+1
+-1.25])
+
+OVSDB_CHECK_POSITIVE([boolean datum],
+ [[parse-data '["boolean"]' '[true]' '[false]' ]],
+ [true
+false])
+
+OVSDB_CHECK_POSITIVE([string datum],
+ [[parse-data '["string"]' '[""]' '["true"]' '["\"\\\/\b\f\n\r\t"]']],
+ [""
+"true"
+"\"\\/\b\f\n\r\t"])
+
+AT_BANNER([OVSDB -- set data])
+
+OVSDB_CHECK_POSITIVE([optional boolean],
+ [[parse-data '{"key": "boolean", "min": 0}' \
+ '["set", [true]]' \
+ '["set", [false]]' \
+ '["set", []]']],
+ [[["set",[true]]
+["set",[false]]
+["set",[]]]],
+ [set])
+
+OVSDB_CHECK_POSITIVE([set of 0 or more integers],
+ [[parse-data '{"key": "integer", "min": 0, "max": "unlimited"}' \
+ '["set", [0]]' \
+ '["set", [0, 1]]' \
+ '["set", [0, 1, 2]]' \
+ '["set", [0, 1, 2, 3, 4, 5]]' \
+ '["set", [0, 1, 2, 3, 4, 5, 6, 7, 8]]' \
+ '["set", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]']],
+ [[["set",[0]]
+["set",[0,1]]
+["set",[0,1,2]]
+["set",[0,1,2,3,4,5]]
+["set",[0,1,2,3,4,5,6,7,8]]
+["set",[0,1,2,3,4,5,6,7,8,9,10]]]])
+
+OVSDB_CHECK_POSITIVE([set of 1 to 3 uuids],
+ [[parse-data '{"key": "uuid", "min": 1, "max": 3}' \
+ '["set", [["uuid", "550e8400-e29b-41d4-a716-446655440000"]]]' \
+ '["set", [["uuid", "c5051240-30ff-43ed-b4b9-93cf3f050813"],
+ ["uuid", "90558331-09af-4d2f-a572-509cad2e9088"],
+ ["uuid", "550e8400-e29b-41d4-a716-446655440000"]]]']],
+ [[["set",[["uuid","550e8400-e29b-41d4-a716-446655440000"]]]
+["set",[["uuid","550e8400-e29b-41d4-a716-446655440000"],["uuid","90558331-09af-4d2f-a572-509cad2e9088"],["uuid","c5051240-30ff-43ed-b4b9-93cf3f050813"]]]]])
+
+OVSDB_CHECK_POSITIVE([set of 0 to 3 strings],
+ [[parse-data '{"key": "string", "min": 0, "max": 3}' \
+ '["set", []]' \
+ '["set", ["a relatively long string"]]' \
+ '["set", ["short string", "a relatively long string"]]' \
+ '["set", ["zzz", "short string", "a relatively long string"]]']],
+ [[["set",[]]
+["set",["a relatively long string"]]
+["set",["a relatively long string","short string"]]
+["set",["a relatively long string","short string","zzz"]]]])
+
+OVSDB_CHECK_NEGATIVE([duplicate boolean not allowed in set],
+ [[parse-data '{"key": "boolean", "max": 5}' '["set", [true, true]]']],
+ [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate integer not allowed in set],
+ [[parse-data '{"key": "integer", "max": 5}' '["set", [1, 2, 3, 1]]']],
+ [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate real not allowed in set],
+ [[parse-data '{"key": "real", "max": 5}' '["set", [0.0, -0.0]]']],
+ [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate string not allowed in set],
+ [[parse-data '{"key": "string", "max": 5}' '["set", ["asdf", "ASDF", "asdf"]]']],
+ [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate uuid not allowed in set],
+ [[parse-data '{"key": "uuid", "max": 5}' \
+ '["set", [["uuid", "7ef21525-0088-4a28-a418-5518413e43ea"],
+ ["uuid", "355ad037-f1da-40aa-b47c-ff9c7e8c6a38"],
+ ["uuid", "7ef21525-0088-4a28-a418-5518413e43ea"]]]']],
+ [ovsdb error: set contains duplicate])
+
+AT_BANNER([OVSDB -- map data])
+
+OVSDB_CHECK_POSITIVE([map of 1 integer to boolean],
+ [[parse-data '{"key": "integer", "value": "boolean"}' \
+ '["map", [[1, true]]]']],
+ [[["map",[[1,true]]]]])
+
+OVSDB_CHECK_POSITIVE([map of at least 1 integer to boolean],
+ [[parse-data '{"key": "integer", "value": "boolean", "max": "unlimited"}' \
+ '["map", [[1, true]]]' \
+ '["map", [[0, true], [1, false], [2, true], [3, true], [4, true]]]' \
+ '["map", [[3, false], [0, true], [4, false]]]']],
+ [[["map",[[1,true]]]
+["map",[[0,true],[1,false],[2,true],[3,true],[4,true]]]
+["map",[[0,true],[3,false],[4,false]]]]])
+
+OVSDB_CHECK_POSITIVE([map of 1 boolean to integer],
+ [[parse-data '{"key": "boolean", "value": "integer"}' \
+ '["map", [[true, 1]]]']],
+ [[["map",[[true,1]]]]])
+
+OVSDB_CHECK_POSITIVE([map of 5 uuid to real],
+ [[parse-data '{"key": "uuid", "value": "real", "min": 5, "max": 5}' \
+ '["map", [[["uuid", "cad8542b-6ee1-486b-971b-7dcbf6e14979"], 1.0],
+ [["uuid", "6b94b968-2702-4f64-9457-314a34d69b8c"], 2.0],
+ [["uuid", "d2c4a168-24de-47eb-a8a3-c1abfc814979"], 3.0],
+ [["uuid", "25bfa475-d072-4f60-8be1-00f48643e9cb"], 4.0],
+ [["uuid", "1c92b8ca-d5e4-4628-a85d-1dc2d099a99a"], 5.0]]]']],
+ [[["map",[[["uuid","1c92b8ca-d5e4-4628-a85d-1dc2d099a99a"],5],[["uuid","25bfa475-d072-4f60-8be1-00f48643e9cb"],4],[["uuid","6b94b968-2702-4f64-9457-314a34d69b8c"],2],[["uuid","cad8542b-6ee1-486b-971b-7dcbf6e14979"],1],[["uuid","d2c4a168-24de-47eb-a8a3-c1abfc814979"],3]]]]])
+
+OVSDB_CHECK_POSITIVE([map of 10 string to string],
+ [[parse-data '{"key": "string", "value": "string", "min": 10, "max": 10}' \
+ '["map", [["2 gills", "1 chopin"],
+ ["2 chopins", "1 pint"],
+ ["2 pints", "1 quart"],
+ ["2 quarts", "1 pottle"],
+ ["2 pottles", "1 gallon"],
+ ["2 gallons", "1 peck"],
+ ["2 pecks", "1 demibushel"],
+ ["2 demibushel", "1 firkin"],
+ ["2 firkins", "1 kilderkin"],
+ ["2 kilderkins", "1 barrel"]]]']],
+ [[["map",[["2 chopins","1 pint"],["2 demibushel","1 firkin"],["2 firkins","1 kilderkin"],["2 gallons","1 peck"],["2 gills","1 chopin"],["2 kilderkins","1 barrel"],["2 pecks","1 demibushel"],["2 pints","1 quart"],["2 pottles","1 gallon"],["2 quarts","1 pottle"]]]]])
+
+OVSDB_CHECK_NEGATIVE([duplicate integer key not allowed in map],
+ [[parse-data '{"key": "integer", "value": "boolean", "max": 5}' \
+ '["map", [[1, true], [2, false], [1, false]]]']],
+ [ovsdb error: map contains duplicate key])
diff --git a/tests/ovsdb-execution.at b/tests/ovsdb-execution.at
new file mode 100644
index 00000000..e6292980
--- /dev/null
+++ b/tests/ovsdb-execution.at
@@ -0,0 +1,321 @@
+AT_BANNER([OVSDB -- execution])
+
+m4_define([ORDINAL_SCHEMA],
+ [['{"name": "mydb",
+ "tables": {
+ "ordinals": {
+ "columns": {
+ "number": {"type": "integer"},
+ "name": {"type": "string"}}}}}']])
+
+# This is like OVSDB_CHECK_POSITIVE, except that UUIDs in the output
+# are replaced by markers of the form <N> where N is a number. The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+m4_define([OVSDB_CHECK_EXECUTION],
+ [AT_SETUP([$1])
+ AT_KEYWORDS([ovsdb execute execution positive $4])
+ OVS_CHECK_LCOV([test-ovsdb execute $2], [0], [stdout], [])
+ AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0], [$3])
+ AT_CLEANUP])
+
+OVSDB_CHECK_EXECUTION([insert row, query table],
+ [ORDINAL_SCHEMA [\
+ '[{"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 0, "name": "zero"}}]' \
+ '[{"op": "select",
+ "table": "ordinals",
+ "where": []}]']],
+ [[[{"uuid":["uuid","<0>"]}]
+[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<1>"],"name":"zero","number":0}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, query by value],
+ [ORDINAL_SCHEMA [\
+ '[{"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 0, "name": "zero"}}]' \
+ '[{"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 1, "name": "one"}}]' \
+ '[{"op": "select",
+ "table": "ordinals",
+ "where": [["name", "==", "zero"]]}]'\
+ '[{"op": "select",
+ "table": "ordinals",
+ "where": [["name", "==", "one"]]}]']],
+ [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"zero","number":0}]}]
+[{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, query by named-uuid],
+ [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": "second"},
+ {"op": "select",
+ "table": "ordinals",
+ "where": [["_uuid", "==", ["named-uuid", "first"]]]},
+ {"op": "select",
+ "table": "ordinals",
+ "where": [["_uuid", "==", ["named-uuid", "second"]]]}]']],
+ [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"zero","number":0}]},{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, update rows by value],
+ [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": "update",
+ "table": "ordinals",
+ "where": [["name", "==", "zero"]],
+ "row": {"name": "nought"}}]' \
+ '[{"op": "select",
+ "table": "ordinals",
+ "where": [],
+ "sort": ["number"]}]']],
+ [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"count":1}]
+[{"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, delete by named-uuid],
+ [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": "second"},
+ {"op": "delete",
+ "table": "ordinals",
+ "where": [["_uuid", "==", ["named-uuid", "first"]]]},
+ {"op": "select",
+ "table": "ordinals",
+ "where": [],
+ "columns": ["name","number"]}]']],
+ [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"count":1},{"rows":[{"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, delete rows by value],
+ [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": "delete",
+ "table": "ordinals",
+ "where": [["name", "==", "zero"]]}]' \
+ '[{"op": "select",
+ "table": "ordinals",
+ "where": []}]']],
+ [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"count":1}]
+[{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<2>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, delete by (non-matching) value],
+ [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": "delete",
+ "table": "ordinals",
+ "where": [["name", "==", "nought"]]}]' \
+ '[{"op": "select",
+ "table": "ordinals",
+ "where": [],
+ "sort": ["number"]}]']],
+ [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"count":0}]
+[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"zero","number":0},{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, delete all],
+ [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": "second"},
+ {"op": "delete",
+ "table": "ordinals",
+ "where": []},
+ {"op": "select",
+ "table": "ordinals",
+ "where": [],
+ "columns": ["name","number"]}]']],
+ [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"count":2},{"rows":[]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert row, query table, commit],
+ [ORDINAL_SCHEMA [\
+ '[{"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 0, "name": "zero"}},
+ {"op": "select",
+ "table": "ordinals",
+ "where": []},
+ {"op": "commit",
+ "durable": false}]']],
+ [[[{"uuid":["uuid","<0>"]},{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<1>"],"name":"zero","number":0}]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert row, query table, commit durably],
+ [ORDINAL_SCHEMA [\
+ '[{"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 0, "name": "zero"}},
+ {"op": "select",
+ "table": "ordinals",
+ "where": []},
+ {"op": "commit",
+ "durable": true}]']],
+ [[[{"uuid":["uuid","<0>"]},{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<1>"],"name":"zero","number":0}]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([equality wait with correct rows],
+ [ORDINAL_SCHEMA [\
+ '[{"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 0, "name": "zero"}},
+ {"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 1, "name": "one"}},
+ {"op": "wait",
+ "timeout": 0,
+ "table": "ordinals",
+ "where": [],
+ "columns": ["name", "number"],
+ "until": "==",
+ "rows": [{"name": "zero", "number": 0},
+ {"name": "one", "number": 1}]}]']],
+ [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([equality wait with extra row],
+ [ORDINAL_SCHEMA [\
+ '[{"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 0, "name": "zero"}},
+ {"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 1, "name": "one"}},
+ {"op": "wait",
+ "timeout": 0,
+ "table": "ordinals",
+ "where": [],
+ "columns": ["name", "number"],
+ "until": "==",
+ "rows": [{"name": "zero", "number": 0},
+ {"name": "one", "number": 1},
+ {"name": "two", "number": 2}]}]']],
+ [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out","error":"timed out"}]
+]])
+
+OVSDB_CHECK_EXECUTION([equality wait with missing row],
+ [ORDINAL_SCHEMA [\
+ '[{"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 0, "name": "zero"}},
+ {"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 1, "name": "one"}},
+ {"op": "wait",
+ "timeout": 0,
+ "table": "ordinals",
+ "where": [],
+ "columns": ["name", "number"],
+ "until": "==",
+ "rows": [{"name": "one", "number": 1}]}]']],
+ [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out","error":"timed out"}]
+]])
+
+OVSDB_CHECK_EXECUTION([inequality wait with correct rows],
+ [ORDINAL_SCHEMA [\
+ '[{"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 0, "name": "zero"}},
+ {"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 1, "name": "one"}},
+ {"op": "wait",
+ "timeout": 0,
+ "table": "ordinals",
+ "where": [],
+ "columns": ["name", "number"],
+ "until": "!=",
+ "rows": [{"name": "zero", "number": 0},
+ {"name": "one", "number": 1}]}]']],
+ [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out","error":"timed out"}]
+]])
+
+OVSDB_CHECK_EXECUTION([inequality wait with extra row],
+ [ORDINAL_SCHEMA [\
+ '[{"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 0, "name": "zero"}},
+ {"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 1, "name": "one"}},
+ {"op": "wait",
+ "timeout": 0,
+ "table": "ordinals",
+ "where": [],
+ "columns": ["name", "number"],
+ "until": "!=",
+ "rows": [{"name": "zero", "number": 0},
+ {"name": "one", "number": 1},
+ {"name": "two", "number": 2}]}]']],
+ [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([inequality wait with missing row],
+ [ORDINAL_SCHEMA [\
+ '[{"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 0, "name": "zero"}},
+ {"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 1, "name": "one"}},
+ {"op": "wait",
+ "timeout": 0,
+ "table": "ordinals",
+ "where": [],
+ "columns": ["name", "number"],
+ "until": "!=",
+ "rows": [{"name": "one", "number": 1}]}]']],
+ [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{}]
+]])
diff --git a/tests/ovsdb-file.at b/tests/ovsdb-file.at
new file mode 100644
index 00000000..c85b29e2
--- /dev/null
+++ b/tests/ovsdb-file.at
@@ -0,0 +1,282 @@
+AT_BANNER([OVSDB -- file I/O])
+
+AT_SETUP([create empty, reread])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+ [test-ovsdb file-io file 'O_CREAT|O_RDWR'], [0],
+ [file: open successful
+], [ignore])
+OVS_CHECK_LCOV(
+ [test-ovsdb file-io file 'O_RDONLY' read], [0],
+ [file: open successful
+file: read: end of file
+], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write one, reread])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+ [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]']], [0],
+ [[file: open successful
+file: write:[0] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+ [test-ovsdb file-io file 'O_RDONLY' read read], [0],
+ [[file: open successful
+file: read: [0]
+file: read: end of file
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([check that O_EXCL works])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+ [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[1]']], [0],
+ [[file: open successful
+file: write:[1] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+ [test-ovsdb file-io file 'O_RDONLY' read], [0],
+ [[file: open successful
+file: read: [1]
+]], [ignore])
+OVS_CHECK_LCOV(
+ [test-ovsdb file-io file 'O_CREAT|O_RDWR|O_EXCL' read], [1],
+ [], [test-ovsdb: I/O error: create: file failed (File exists)
+])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write one, reread])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+ [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0],
+ [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+ [test-ovsdb file-io file 'O_RDONLY' read read read read], [0],
+ [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read: end of file
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write one, reread, append])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+ [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0],
+ [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+ [[test-ovsdb file-io file 'O_RDWR' read read read 'write:["append"]']], [0],
+ [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: write:["append"] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+ [test-ovsdb file-io file 'O_RDONLY' read read read read read], [0],
+ [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read: ["append"]
+file: read: end of file
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, reread one, overwrite])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+ [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0],
+ [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+ [[test-ovsdb file-io file 'O_RDWR' read 'write:["more data"]']], [0],
+ [[file: open successful
+file: read: [0]
+file: write:["more data"] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+ [test-ovsdb file-io file 'O_RDONLY' read read read], [0],
+ [[file: open successful
+file: read: [0]
+file: read: ["more data"]
+file: read: end of file
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, add corrupted data, read])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+ [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0],
+ [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([echo 'xxx' >> file])
+OVS_CHECK_LCOV(
+ [test-ovsdb file-io file 'O_RDONLY' read read read read], [0],
+ [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read failed: syntax error: file: parse error at offset 174 in header line "xxx"
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, add corrupted data, read, overwrite])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+ [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0],
+ [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([echo 'xxx' >> file])
+OVS_CHECK_LCOV(
+ [[test-ovsdb file-io file 'O_RDWR' read read read read 'write:[3]']], [0],
+ [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read failed: syntax error: file: parse error at offset 174 in header line "xxx"
+file: write:[3] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+ [test-ovsdb file-io file 'O_RDONLY' read read read read read], [0],
+ [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read: [3]
+file: read: end of file
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, corrupt some data, read, overwrite])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+ [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0],
+ [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([[sed 's/\[2]/[3]/' < file > file.tmp]])
+AT_CHECK([mv file.tmp file])
+AT_CHECK([[grep -c '\[3]' file]], [0], [1
+])
+OVS_CHECK_LCOV(
+ [[test-ovsdb file-io file 'O_RDWR' read read read 'write:["longer data"]']], [0],
+ [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read failed: syntax error: file: 4 bytes starting at offset 170 have SHA-1 hash 5c031e5c0d3a9338cc127ebe40bb2748b6a67e78 but should have hash 98f55556e7ffd432381b56a19bd485b3e6446442
+file: write:["longer data"] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+ [test-ovsdb file-io file 'O_RDONLY' read read read read], [0],
+ [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: ["longer data"]
+file: read: end of file
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, truncate file, read, overwrite])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+ [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0],
+ [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([[sed 's/\[2]/2/' < file > file.tmp]])
+AT_CHECK([mv file.tmp file])
+AT_CHECK([[grep -c '^2$' file]], [0], [1
+])
+OVS_CHECK_LCOV(
+ [[test-ovsdb file-io file 'O_RDWR' read read read 'write:["longer data"]']], [0],
+ [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read failed: I/O error: file: error reading 4 bytes starting at offset 170 (unexpected end of file)
+file: write:["longer data"] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+ [test-ovsdb file-io file 'O_RDONLY' read read read read], [0],
+ [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: ["longer data"]
+file: read: end of file
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write bad JSON, read, overwrite])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+ [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0],
+ [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([[printf '%s\n%s\n' 'OVSDB JSON 5 d910b02871075d3156ec8675dfc95b7d5d640aa6' 'null' >> file]])
+OVS_CHECK_LCOV(
+ [[test-ovsdb file-io file 'O_RDWR' read read read read 'write:["replacement data"]']], [0],
+ [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read failed: syntax error: file: 5 bytes starting at offset 228 are not valid JSON (syntax error at beginning of input)
+file: write:["replacement data"] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+ [test-ovsdb file-io file 'O_RDONLY' read read read read read], [0],
+ [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read: ["replacement data"]
+file: read: end of file
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
diff --git a/tests/ovsdb-query.at b/tests/ovsdb-query.at
new file mode 100644
index 00000000..c39aa121
--- /dev/null
+++ b/tests/ovsdb-query.at
@@ -0,0 +1,535 @@
+AT_BANNER([OVSDB -- queries])
+
+OVSDB_CHECK_POSITIVE([queries on scalars],
+ [[query \
+ '{"columns":
+ {"i": {"type": "integer"},
+ "r": {"type": "real"},
+ "b": {"type": "boolean"},
+ "s": {"type": "string"},
+ "u": {"type": "uuid"}}}' \
+ '[{"i": 0,
+ "r": 0.5,
+ "b": true,
+ "s": "a",
+ "u": ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]},
+ {"i": 1,
+ "r": 1.5,
+ "b": false,
+ "s": "b",
+ "u": ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]},
+ {"i": 2,
+ "r": 2.5,
+ "b": true,
+ "s": "c",
+ "u": ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]},
+ {"i": 3,
+ "r": 3.5,
+ "b": false,
+ "s": "d",
+ "u": ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]},
+ {"i": 4,
+ "r": 4.5,
+ "b": true,
+ "s": "e",
+ "u": ["uuid", "4a5127e2-0256-4a72-a7dc-6246213967c7"]}]' \
+ '[[],
+ [["i", "==", 0]],
+ [["i", "!=", 1]],
+ [["i", "<", 2]],
+ [["i", "<=", 3]],
+ [["i", ">", 2]],
+ [["i", ">=", 4]],
+ [["i", "includes", 3]],
+ [["i", "excludes", 2]],
+ [["r", "==", 0.5]],
+ [["r", "!=", 1.5]],
+ [["r", "<", 2.5]],
+ [["r", "<=", 3.5]],
+ [["r", ">", 4.5]],
+ [["r", ">=", 5.5]],
+ [["r", "includes", 1]],
+ [["r", "excludes", 3]],
+ [["b", "==", true]],
+ [["b", "!=", true]],
+ [["b", "includes", false]],
+ [["b", "excludes", true]],
+ [["s", "==", "a"]],
+ [["s", "!=", "b"]],
+ [["s", "includes", "c"]],
+ [["s", "excludes", "d"]],
+ [["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]],
+ [["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]],
+ [["u", "includes",["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]']],
+ [dnl
+query 0: 11111
+query 1: 1----
+query 2: 1-111
+query 3: 11---
+query 4: 1111-
+query 5: ---11
+query 6: ----1
+query 7: ---1-
+query 8: 11-11
+query 9: 1----
+query 10: 1-111
+query 11: 11---
+query 12: 1111-
+query 13: -----
+query 14: -----
+query 15: -----
+query 16: 11111
+query 17: 1-1-1
+query 18: -1-1-
+query 19: -1-1-
+query 20: -1-1-
+query 21: 1----
+query 22: 1-111
+query 23: --1--
+query 24: 111-1
+query 25: 1----
+query 26: 1-111
+query 27: --1--],
+ [query])
+
+OVSDB_CHECK_POSITIVE([queries on sets],
+ [[query \
+ '{"columns": {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}}}}' \
+ '[{"i": ["set", []]},
+ {"i": ["set", [0]]},
+ {"i": ["set", [1]]},
+ {"i": ["set", [0, 1]]},
+ {"i": ["set", [2]]},
+ {"i": ["set", [2, 0]]},
+ {"i": ["set", [2, 1]]},
+ {"i": ["set", [2, 1, 0]]}]' \
+ '[[],
+ [["i", "==", ["set", []]]],
+ [["i", "==", ["set", [0]]]],
+ [["i", "==", ["set", [1]]]],
+ [["i", "==", ["set", [0, 1]]]],
+ [["i", "==", ["set", [2]]]],
+ [["i", "==", ["set", [2, 0]]]],
+ [["i", "==", ["set", [2, 1]]]],
+ [["i", "==", ["set", [2, 1, 0]]]],
+ [["i", "!=", ["set", []]]],
+ [["i", "!=", ["set", [0]]]],
+ [["i", "!=", ["set", [1]]]],
+ [["i", "!=", ["set", [0, 1]]]],
+ [["i", "!=", ["set", [2]]]],
+ [["i", "!=", ["set", [2, 0]]]],
+ [["i", "!=", ["set", [2, 1]]]],
+ [["i", "!=", ["set", [2, 1, 0]]]],
+ [["i", "includes", ["set", []]]],
+ [["i", "includes", ["set", [0]]]],
+ [["i", "includes", ["set", [1]]]],
+ [["i", "includes", ["set", [0, 1]]]],
+ [["i", "includes", ["set", [2]]]],
+ [["i", "includes", ["set", [2, 0]]]],
+ [["i", "includes", ["set", [2, 1]]]],
+ [["i", "includes", ["set", [2, 1, 0]]]],
+ [["i", "excludes", ["set", []]]],
+ [["i", "excludes", ["set", [0]]]],
+ [["i", "excludes", ["set", [1]]]],
+ [["i", "excludes", ["set", [0, 1]]]],
+ [["i", "excludes", ["set", [2]]]],
+ [["i", "excludes", ["set", [2, 0]]]],
+ [["i", "excludes", ["set", [2, 1]]]],
+ [["i", "excludes", ["set", [2, 1, 0]]]]]']],
+ [dnl
+query 0: 11111 111
+query 1: 1---- ---
+query 2: -1--- ---
+query 3: --1-- ---
+query 4: ---1- ---
+query 5: ----1 ---
+query 6: ----- 1--
+query 7: ----- -1-
+query 8: ----- --1
+query 9: -1111 111
+query 10: 1-111 111
+query 11: 11-11 111
+query 12: 111-1 111
+query 13: 1111- 111
+query 14: 11111 -11
+query 15: 11111 1-1
+query 16: 11111 11-
+query 17: 11111 111
+query 18: -1-1- 1-1
+query 19: --11- -11
+query 20: ---1- --1
+query 21: ----1 111
+query 22: ----- 1-1
+query 23: ----- -11
+query 24: ----- --1
+query 25: 11111 111
+query 26: 1-1-1 -1-
+query 27: 11--1 1--
+query 28: 1---1 ---
+query 29: 1111- ---
+query 30: 1-1-- ---
+query 31: 11--- ---
+query 32: 1---- ---], [query])
+
+# This is the same as the "set" test except that it adds values,
+# all of which always match.
+OVSDB_CHECK_POSITIVE([queries on maps (1)],
+ [[query \
+ '{"columns": {"i": {"type": {"key": "integer",
+ "value": "boolean",
+ "min": 0,
+ "max": "unlimited"}}}}' \
+ '[{"i": ["map", []]},
+ {"i": ["map", [[0, true]]]},
+ {"i": ["map", [[1, false]]]},
+ {"i": ["map", [[0, true], [1, false]]]},
+ {"i": ["map", [[2, true]]]},
+ {"i": ["map", [[2, true], [0, true]]]},
+ {"i": ["map", [[2, true], [1, false]]]},
+ {"i": ["map", [[2, true], [1, false], [0, true]]]}]' \
+ '[[],
+ [["i", "==", ["map", []]]],
+ [["i", "==", ["map", [[0, true]]]]],
+ [["i", "==", ["map", [[1, false]]]]],
+ [["i", "==", ["map", [[0, true], [1, false]]]]],
+ [["i", "==", ["map", [[2, true]]]]],
+ [["i", "==", ["map", [[2, true], [0, true]]]]],
+ [["i", "==", ["map", [[2, true], [1, false]]]]],
+ [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]],
+ [["i", "!=", ["map", []]]],
+ [["i", "!=", ["map", [[0, true]]]]],
+ [["i", "!=", ["map", [[1, false]]]]],
+ [["i", "!=", ["map", [[0, true], [1, false]]]]],
+ [["i", "!=", ["map", [[2, true]]]]],
+ [["i", "!=", ["map", [[2, true], [0, true]]]]],
+ [["i", "!=", ["map", [[2, true], [1, false]]]]],
+ [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]],
+ [["i", "includes", ["map", []]]],
+ [["i", "includes", ["map", [[0, true]]]]],
+ [["i", "includes", ["map", [[1, false]]]]],
+ [["i", "includes", ["map", [[0, true], [1, false]]]]],
+ [["i", "includes", ["map", [[2, true]]]]],
+ [["i", "includes", ["map", [[2, true], [0, true]]]]],
+ [["i", "includes", ["map", [[2, true], [1, false]]]]],
+ [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]],
+ [["i", "excludes", ["map", []]]],
+ [["i", "excludes", ["map", [[0, true]]]]],
+ [["i", "excludes", ["map", [[1, false]]]]],
+ [["i", "excludes", ["map", [[0, true], [1, false]]]]],
+ [["i", "excludes", ["map", [[2, true]]]]],
+ [["i", "excludes", ["map", [[2, true], [0, true]]]]],
+ [["i", "excludes", ["map", [[2, true], [1, false]]]]],
+ [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]']],
+ [dnl
+query 0: 11111 111
+query 1: 1---- ---
+query 2: -1--- ---
+query 3: --1-- ---
+query 4: ---1- ---
+query 5: ----1 ---
+query 6: ----- 1--
+query 7: ----- -1-
+query 8: ----- --1
+query 9: -1111 111
+query 10: 1-111 111
+query 11: 11-11 111
+query 12: 111-1 111
+query 13: 1111- 111
+query 14: 11111 -11
+query 15: 11111 1-1
+query 16: 11111 11-
+query 17: 11111 111
+query 18: -1-1- 1-1
+query 19: --11- -11
+query 20: ---1- --1
+query 21: ----1 111
+query 22: ----- 1-1
+query 23: ----- -11
+query 24: ----- --1
+query 25: 11111 111
+query 26: 1-1-1 -1-
+query 27: 11--1 1--
+query 28: 1---1 ---
+query 29: 1111- ---
+query 30: 1-1-- ---
+query 31: 11--- ---
+query 32: 1---- ---], [query])
+
+# This is the same as the "set" test except that it adds values,
+# and those values don't always match.
+OVSDB_CHECK_POSITIVE([queries on maps (2)],
+ [[query \
+ '{"columns": {"i": {"type": {"key": "integer",
+ "value": "boolean",
+ "min": 0,
+ "max": "unlimited"}}}}' \
+ '[{"i": ["map", []]},
+ {"i": ["map", [[0, true]]]},
+ {"i": ["map", [[0, false]]]},
+ {"i": ["map", [[1, false]]]},
+ {"i": ["map", [[1, true]]]},
+
+ {"i": ["map", [[0, true], [1, false]]]},
+ {"i": ["map", [[0, true], [1, true]]]},
+ {"i": ["map", [[2, true]]]},
+ {"i": ["map", [[2, false]]]},
+ {"i": ["map", [[2, true], [0, true]]]},
+
+ {"i": ["map", [[2, false], [0, true]]]},
+ {"i": ["map", [[2, true], [1, false]]]},
+ {"i": ["map", [[2, true], [1, true]]]},
+ {"i": ["map", [[2, true], [1, false], [0, true]]]},
+ {"i": ["map", [[2, true], [1, false], [0, false]]]}]' \
+ '[[],
+ [["i", "==", ["map", []]]],
+ [["i", "==", ["map", [[0, true]]]]],
+ [["i", "==", ["map", [[1, false]]]]],
+ [["i", "==", ["map", [[0, true], [1, false]]]]],
+ [["i", "==", ["map", [[2, true]]]]],
+ [["i", "==", ["map", [[2, true], [0, true]]]]],
+ [["i", "==", ["map", [[2, true], [1, false]]]]],
+ [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]],
+ [["i", "!=", ["map", []]]],
+ [["i", "!=", ["map", [[0, true]]]]],
+ [["i", "!=", ["map", [[1, false]]]]],
+ [["i", "!=", ["map", [[0, true], [1, false]]]]],
+ [["i", "!=", ["map", [[2, true]]]]],
+ [["i", "!=", ["map", [[2, true], [0, true]]]]],
+ [["i", "!=", ["map", [[2, true], [1, false]]]]],
+ [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]],
+ [["i", "includes", ["map", []]]],
+ [["i", "includes", ["map", [[0, true]]]]],
+ [["i", "includes", ["map", [[1, false]]]]],
+ [["i", "includes", ["map", [[0, true], [1, false]]]]],
+ [["i", "includes", ["map", [[2, true]]]]],
+ [["i", "includes", ["map", [[2, true], [0, true]]]]],
+ [["i", "includes", ["map", [[2, true], [1, false]]]]],
+ [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]],
+ [["i", "excludes", ["map", []]]],
+ [["i", "excludes", ["map", [[0, true]]]]],
+ [["i", "excludes", ["map", [[1, false]]]]],
+ [["i", "excludes", ["map", [[0, true], [1, false]]]]],
+ [["i", "excludes", ["map", [[2, true]]]]],
+ [["i", "excludes", ["map", [[2, true], [0, true]]]]],
+ [["i", "excludes", ["map", [[2, true], [1, false]]]]],
+ [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]']],
+ [dnl
+query 0: 11111 11111 11111
+query 1: 1---- ----- -----
+query 2: -1--- ----- -----
+query 3: ---1- ----- -----
+query 4: ----- 1---- -----
+query 5: ----- --1-- -----
+query 6: ----- ----1 -----
+query 7: ----- ----- -1---
+query 8: ----- ----- ---1-
+query 9: -1111 11111 11111
+query 10: 1-111 11111 11111
+query 11: 111-1 11111 11111
+query 12: 11111 -1111 11111
+query 13: 11111 11-11 11111
+query 14: 11111 1111- 11111
+query 15: 11111 11111 1-111
+query 16: 11111 11111 111-1
+query 17: 11111 11111 11111
+query 18: -1--- 11--1 1--1-
+query 19: ---1- 1---- -1-11
+query 20: ----- 1---- ---1-
+query 21: ----- --1-1 -1111
+query 22: ----- ----1 ---1-
+query 23: ----- ----- -1-11
+query 24: ----- ----- ---1-
+query 25: 11111 11111 11111
+query 26: 1-111 --11- -11-1
+query 27: 111-1 -1111 1-1--
+query 28: 1-1-1 --11- --1--
+query 29: 11111 11-1- 1----
+query 30: 1-111 ---1- -----
+query 31: 111-1 -1-1- 1----
+query 32: 1-1-1 ---1- -----], [query])
+
+OVSDB_CHECK_POSITIVE([UUID-distinct queries on scalars],
+ [[query-distinct \
+ '{"columns":
+ {"i": {"type": "integer"},
+ "r": {"type": "real"},
+ "b": {"type": "boolean"},
+ "s": {"type": "string"},
+ "u": {"type": "uuid"}}}' \
+ '[{"i": 0,
+ "r": 0.5,
+ "b": true,
+ "s": "a",
+ "u": ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]},
+ {"i": 1,
+ "r": 1.5,
+ "b": false,
+ "s": "b",
+ "u": ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]},
+ {"i": 2,
+ "r": 2.5,
+ "b": true,
+ "s": "c",
+ "u": ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]},
+ {"i": 3,
+ "r": 3.5,
+ "b": false,
+ "s": "d",
+ "u": ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]},
+ {"i": 4,
+ "r": 4.5,
+ "b": true,
+ "s": "e",
+ "u": ["uuid", "4a5127e2-0256-4a72-a7dc-6246213967c7"]}]' \
+ '[[],
+ [["i", "==", 0]],
+ [["i", "!=", 1]],
+ [["i", "<", 2]],
+ [["i", "<=", 3]],
+ [["i", ">", 2]],
+ [["i", ">=", 4]],
+ [["i", "includes", 3]],
+ [["i", "excludes", 2]],
+ [["r", "==", 0.5]],
+ [["r", "!=", 1.5]],
+ [["r", "<", 2.5]],
+ [["r", "<=", 3.5]],
+ [["r", ">", 4.5]],
+ [["r", ">=", 5.5]],
+ [["r", "includes", 1]],
+ [["r", "excludes", 3]],
+ [["b", "==", true]],
+ [["b", "!=", true]],
+ [["b", "includes", false]],
+ [["b", "excludes", true]],
+ [["s", "==", "a"]],
+ [["s", "!=", "b"]],
+ [["s", "includes", "c"]],
+ [["s", "excludes", "d"]],
+ [["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]],
+ [["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]],
+ [["u", "includes",["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]' \
+ '["_uuid"]']],
+ [dnl
+query 0: abcde
+query 1: a----
+query 2: a-cde
+query 3: ab---
+query 4: abcd-
+query 5: ---de
+query 6: ----e
+query 7: ---d-
+query 8: ab-de
+query 9: a----
+query 10: a-cde
+query 11: ab---
+query 12: abcd-
+query 13: -----
+query 14: -----
+query 15: -----
+query 16: abcde
+query 17: a-c-e
+query 18: -b-d-
+query 19: -b-d-
+query 20: -b-d-
+query 21: a----
+query 22: a-cde
+query 23: --c--
+query 24: abc-e
+query 25: a----
+query 26: a-cde
+query 27: --c--],
+ [query])
+
+OVSDB_CHECK_POSITIVE([Boolean-distinct queries on scalars],
+ [[query-distinct \
+ '{"columns":
+ {"i": {"type": "integer"},
+ "r": {"type": "real"},
+ "b": {"type": "boolean"},
+ "s": {"type": "string"},
+ "u": {"type": "uuid"}}}' \
+ '[{"i": 0,
+ "r": 0.5,
+ "b": true,
+ "s": "a",
+ "u": ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]},
+ {"i": 1,
+ "r": 1.5,
+ "b": false,
+ "s": "b",
+ "u": ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]},
+ {"i": 2,
+ "r": 2.5,
+ "b": true,
+ "s": "c",
+ "u": ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]},
+ {"i": 3,
+ "r": 3.5,
+ "b": false,
+ "s": "d",
+ "u": ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]},
+ {"i": 4,
+ "r": 4.5,
+ "b": true,
+ "s": "e",
+ "u": ["uuid", "4a5127e2-0256-4a72-a7dc-6246213967c7"]}]' \
+ '[[],
+ [["i", "==", 0]],
+ [["i", "!=", 1]],
+ [["i", "<", 2]],
+ [["i", "<=", 3]],
+ [["i", ">", 2]],
+ [["i", ">=", 4]],
+ [["i", "includes", 3]],
+ [["i", "excludes", 2]],
+ [["r", "==", 0.5]],
+ [["r", "!=", 1.5]],
+ [["r", "<", 2.5]],
+ [["r", "<=", 3.5]],
+ [["r", ">", 4.5]],
+ [["r", ">=", 5.5]],
+ [["r", "includes", 1]],
+ [["r", "excludes", 3]],
+ [["b", "==", true]],
+ [["b", "!=", true]],
+ [["b", "includes", false]],
+ [["b", "excludes", true]],
+ [["s", "==", "a"]],
+ [["s", "!=", "b"]],
+ [["s", "includes", "c"]],
+ [["s", "excludes", "d"]],
+ [["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]],
+ [["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]],
+ [["u", "includes",["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]' \
+ '["b"]']],
+ [dnl
+query 0: ababa
+query 1: a-a-a
+query 2: ababa
+query 3: ababa
+query 4: ababa
+query 5: ababa
+query 6: a-a-a
+query 7: -b-b-
+query 8: ababa
+query 9: a-a-a
+query 10: ababa
+query 11: ababa
+query 12: ababa
+query 13: -----
+query 14: -----
+query 15: -----
+query 16: ababa
+query 17: a-a-a
+query 18: -b-b-
+query 19: -b-b-
+query 20: -b-b-
+query 21: a-a-a
+query 22: ababa
+query 23: a-a-a
+query 24: ababa
+query 25: a-a-a
+query 26: ababa
+query 27: a-a-a],
+ [query])
diff --git a/tests/ovsdb-row.at b/tests/ovsdb-row.at
new file mode 100644
index 00000000..e631f6f8
--- /dev/null
+++ b/tests/ovsdb-row.at
@@ -0,0 +1,277 @@
+AT_BANNER([OVSDB -- rows])
+
+# Autoconf 2.63 has a bug that causes the double-quotes below to be
+# lost, so that the following tests fail, so we mark them as XFAIL for
+# Autoconf < 2.64.
+
+m4_define([RESERVED_COLUMNS], [["_uuid":["uuid","00000000-0000-0000-0000-000000000000"],"_version":["uuid","00000000-0000-0000-0000-000000000000"]]])
+
+OVSDB_CHECK_POSITIVE([row with one string column],
+ [[parse-rows \
+ '{"columns": {"name": {"type": "string"}}}' \
+ '{"name": "value"}' \
+ '{"name": ""}' \
+ '{"name": "longer string with spaces"}' \
+ '{}']],
+ [{RESERVED_COLUMNS,"name":"value"}
+name
+{RESERVED_COLUMNS,"name":""}
+name
+{RESERVED_COLUMNS,"name":"longer string with spaces"}
+name
+{RESERVED_COLUMNS,"name":""}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with one integer column],
+ [[parse-rows \
+ '{"columns": {"count": {"type": "integer"}}}' \
+ '{"count": 1}' \
+ '{"count": -1}' \
+ '{"count": 2e10}' \
+ '{}']],
+ [{RESERVED_COLUMNS,"count":1}
+count
+{RESERVED_COLUMNS,"count":-1}
+count
+{RESERVED_COLUMNS,"count":20000000000}
+count
+{RESERVED_COLUMNS,"count":0}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with one real column],
+ [[parse-rows \
+ '{"columns": {"cost": {"type": "real"}}}' \
+ '{"cost": 1.0}' \
+ '{"cost": -2.0}' \
+ '{"cost": 123000}' \
+ '{}']],
+ [{RESERVED_COLUMNS,"cost":1}
+cost
+{RESERVED_COLUMNS,"cost":-2}
+cost
+{RESERVED_COLUMNS,"cost":123000}
+cost
+{RESERVED_COLUMNS,"cost":0}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with one boolean column],
+ [[parse-rows \
+ '{"columns": {"feasible": {"type": "boolean"}}}' \
+ '{"feasible": true}' \
+ '{"feasible": false}' \
+ '{}']],
+ [{RESERVED_COLUMNS,"feasible":true}
+feasible
+{RESERVED_COLUMNS,"feasible":false}
+feasible
+{RESERVED_COLUMNS,"feasible":false}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with one uuid column],
+ [[parse-rows \
+ '{"columns": {"ref": {"type": "uuid"}}}' \
+ '{"ref": ["uuid", "f707423d-bf5b-48b5-b6c0-797c900ba4b6"]}' \
+ '{"ref": ["uuid", "33583cc5-d2f4-43de-b1ca-8aac14071b51"]}' \
+ '{}']],
+ [{RESERVED_COLUMNS,"ref":[["uuid","f707423d-bf5b-48b5-b6c0-797c900ba4b6"]]}
+ref
+{RESERVED_COLUMNS,"ref":[["uuid","33583cc5-d2f4-43de-b1ca-8aac14071b51"]]}
+ref
+{RESERVED_COLUMNS,"ref":[["uuid","00000000-0000-0000-0000-000000000000"]]}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with set of 1 to 2 elements],
+ [[parse-rows \
+ '{"columns": {"myset": {"type": {"key": "integer", "min": 1, "max": 2}}}}' \
+ '{}']],
+ [{RESERVED_COLUMNS,["myset":["set",[0]]]}
+<none>])
+
+OVSDB_CHECK_POSITIVE([row with map of 1 to 2 elements],
+ [[parse-rows \
+ '{"columns": {"mymap": {"type": {"key": "integer", "value": "uuid", "min": 1, "max": 2}}}}' \
+ '{}']],
+ [{RESERVED_COLUMNS,["mymap":["map",[[0,["uuid","00000000-0000-0000-0000-000000000000"]]]]]}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with several columns],
+ [[parse-rows \
+ '{"columns":
+ {"vswitch": {"type": "uuid"},
+ "name": {"type": "string"},
+ "datapath_id": {"type": {"key": "string", "min": 0}},
+ "hwaddr": {"type": "string"},
+ "mirrors": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+ "netflows": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+ "controller": {"type": {"key": "uuid", "min": 0}},
+ "listeners": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+ "snoops": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \
+ '{"vswitch": ["uuid", "1a5c7280-0d4c-4e34-9ec7-c772339f7774"],
+ "name": "br0",
+ "datapath_id": ["set", ["000ae4256bb0"]],
+ "hwaddr": "00:0a:e4:25:6b:b0"}' \
+ '{}']],
+ [{RESERVED_COLUMNS,["controller":["set",[]],"datapath_id":["set",["000ae4256bb0"]],"hwaddr":"00:0a:e4:25:6b:b0","listeners":["set",[]],"mirrors":["set",[]],"name":"br0","netflows":["set",[]],"snoops":["set",[]],"vswitch":["uuid","1a5c7280-0d4c-4e34-9ec7-c772339f7774"]]}
+datapath_id, hwaddr, name, vswitch
+{RESERVED_COLUMNS,["controller":["set",[]],"datapath_id":["set",[]],"hwaddr":"","listeners":["set",[]],"mirrors":["set",[]],"name":"","netflows":["set",[]],"snoops":["set",[]],"vswitch":["uuid","00000000-0000-0000-0000-000000000000"]]}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row hashing (scalars)],
+ [[compare-rows \
+ '{"columns":
+ {"i": {"type": "integer"},
+ "r": {"type": "real"},
+ "b": {"type": "boolean"},
+ "s": {"type": "string"},
+ "u": {"type": "uuid"}}}' \
+ '["null", {}]' \
+ '["i1", {"i": 1}]' \
+ '["i2", {"i": 2}]' \
+ '["i4", {"i": 4}]' \
+ '["i8", {"i": 8}]' \
+ '["i16", {"i": 16}]' \
+ '["i32", {"i": 32}]' \
+ '["i64", {"i": 64}]' \
+ '["i128", {"i": 128}]' \
+ '["i256", {"i": 256}]' \
+ '["null2", {"r": -0}]' \
+ '["r123", {"r": 123}]' \
+ '["r0.0625", {"r": 0.0625}]' \
+ '["r0.125", {"r": 0.125}]' \
+ '["r0.25", {"r": 0.25}]' \
+ '["r0.5", {"r": 0.5}]' \
+ '["r1", {"r": 1}]' \
+ '["r2", {"r": 2}]' \
+ '["r4", {"r": 4}]' \
+ '["r8", {"r": 8}]' \
+ '["r16", {"r": 16}]' \
+ '["r32", {"r": 32}]' \
+ '["null3", {"b": false}]' \
+ '["b1", {"b": true}]' \
+ '["null4", {"s": ""}]' \
+ '["s0", {"s": "a"}]' \
+ '["s1", {"s": "b"}]' \
+ '["s2", {"s": "c"}]' \
+ '["s3", {"s": "d"}]' \
+ '["s4", {"s": "e"}]' \
+ '["s5", {"s": "f"}]' \
+ '["s6", {"s": "g"}]' \
+ '["s7", {"s": "h"}]' \
+ '["s8", {"s": "i"}]' \
+ '["s9", {"s": "j"}]' \
+ '["null5", {"u": ["uuid","00000000-0000-0000-0000-000000000000"]}]' \
+ '["u1", {"u": ["uuid","10000000-0000-0000-0000-000000000000"]}]' \
+ '["u2", {"u": ["uuid","01000000-0000-0000-0000-000000000000"]}]' \
+ '["u3", {"u": ["uuid","00100000-0000-0000-0000-000000000000"]}]' \
+ '["u4", {"u": ["uuid","00010000-0000-0000-0000-000000000000"]}]' \
+ '["u5", {"u": ["uuid","00001000-0000-0000-0000-000000000000"]}]' \
+ '["u6", {"u": ["uuid","00000100-0000-0000-0000-000000000000"]}]' \
+ '["u7", {"u": ["uuid","00000010-0000-0000-0000-000000000000"]}]' \
+ '["u8", {"u": ["uuid","00000001-0000-0000-0000-000000000000"]}]' \
+ '["null6", {"u": ["uuid","00000000-c6db-4d22-970f-b41fabd20c4b"]}]']],
+ [[null == null2
+null == null3
+null == null4
+null == null5
+hash(null) == hash(null6)
+null2 == null3
+null2 == null4
+null2 == null5
+hash(null2) == hash(null6)
+null3 == null4
+null3 == null5
+hash(null3) == hash(null6)
+null4 == null5
+hash(null4) == hash(null6)
+hash(null5) == hash(null6)]])
+
+OVSDB_CHECK_POSITIVE([row hashing (sets)],
+ [[compare-rows \
+ '{"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"}}}}' \
+ '["null0", {"i": ["set", []]}]' \
+ '["i0", {"i": ["set", [0]]}]' \
+ '["i01", {"i": ["set", [0, 1]]}]' \
+ '["i012", {"i": ["set", [0, 1, 2]]}]' \
+ '["i021", {"i": ["set", [0, 2, 1]]}]' \
+ '["i201", {"i": ["set", [2, 0, 1]]}]' \
+ '["i102", {"i": ["set", [1, 0, 2]]}]' \
+ '["i120", {"i": ["set", [1, 2, 0]]}]' \
+ '["i210", {"i": ["set", [2, 1, 0]]}]' \
+ '["r0", {"r": ["set", [0]]}]' \
+ '["r01", {"r": ["set", [0, 1]]}]' \
+ '["r012", {"r": ["set", [0, 1, 2]]}]' \
+ '["r201", {"r": ["set", [2, 0, 1]]}]' \
+ '["null1", {"b": ["set", []]}]' \
+ '["b0", {"b": ["set", [false]]}]' \
+ '["b1", {"b": ["set", [true]]}]' \
+ '["b01", {"b": ["set", [false, true]]}]' \
+ '["b10", {"b": ["set", [true, false]]}]' \
+ '["null2", {"s": ["set", []]}]' \
+ '["sa", {"s": ["set", ["a"]]}]' \
+ '["sb", {"s": ["set", ["b"]]}]' \
+ '["sab", {"s": ["set", ["a", "b"]]}]' \
+ '["sba", {"s": ["set", ["b", "a"]]}]']],
+ [[null0 == null1
+null0 == null2
+i012 == i021
+i012 == i201
+i012 == i102
+i012 == i120
+i012 == i210
+i021 == i201
+i021 == i102
+i021 == i120
+i021 == i210
+i201 == i102
+i201 == i120
+i201 == i210
+i102 == i120
+i102 == i210
+i120 == i210
+r012 == r201
+null1 == null2
+b01 == b10
+sab == sba]])
+
+OVSDB_CHECK_POSITIVE([row hashing (maps)],
+ [[compare-rows \
+ '{"columns":
+ {"ii": {"type": {"key": "integer", "value": "integer",
+ "min": 0, "max": "unlimited"}},
+ "rr": {"type": {"key": "real", "value": "real",
+ "min": 0, "max": "unlimited"}},
+ "bb": {"type": {"key": "boolean", "value": "boolean",
+ "min": 0, "max": "unlimited"}},
+ "ss": {"type": {"key": "string", "value": "string",
+ "min": 0, "max": "unlimited"}}}}' \
+ '["null", {}]' \
+ '["ii0", {"ii": ["map", [[0, 0]]]}]' \
+ '["ii1", {"ii": ["map", [[0, 1]]]}]' \
+ '["ii00", {"ii": ["map", [[0, 0], [1, 0]]]}]' \
+ '["ii01", {"ii": ["map", [[0, 0], [1, 1]]]}]' \
+ '["ii10", {"ii": ["map", [[0, 1], [1, 0]]]}]' \
+ '["ii11", {"ii": ["map", [[0, 1], [1, 1]]]}]' \
+ '["rr0", {"rr": ["map", [[0, 0]]]}]' \
+ '["rr0", {"rr": ["map", [[0, 1]]]}]' \
+ '["rr00", {"rr": ["map", [[0, 0], [1, 0]]]}]' \
+ '["rr01", {"rr": ["map", [[0, 0], [1, 1]]]}]' \
+ '["rr10", {"rr": ["map", [[0, 1], [1, 0]]]}]' \
+ '["rr11", {"rr": ["map", [[0, 1], [1, 1]]]}]' \
+ '["bb0", {"bb": ["map", [[false, false]]]}]' \
+ '["bb1", {"bb": ["map", [[false, true]]]}]' \
+ '["bb00", {"bb": ["map", [[false, false], [true, false]]]}]' \
+ '["bb01", {"bb": ["map", [[false, false], [true, true]]]}]' \
+ '["bb10", {"bb": ["map", [[false, true], [true, false]]]}]' \
+ '["bb11", {"bb": ["map", [[false, true], [true, true]]]}]' \
+ '["ss0", {"ss": ["map", [["a", "a"]]]}]' \
+ '["ss1", {"ss": ["map", [["a", "b"]]]}]' \
+ '["ss00", {"ss": ["map", [["a", "a"], ["b", "a"]]]}]' \
+ '["ss01", {"ss": ["map", [["a", "a"], ["b", "b"]]]}]' \
+ '["ss10", {"ss": ["map", [["a", "b"], ["b", "a"]]]}]' \
+ '["ss11", {"ss": ["map", [["a", "b"], ["b", "b"]]]}]'; echo
+]], [[]])
diff --git a/tests/ovsdb-table.at b/tests/ovsdb-table.at
new file mode 100644
index 00000000..ebc5992d
--- /dev/null
+++ b/tests/ovsdb-table.at
@@ -0,0 +1,31 @@
+AT_BANNER([OVSDB -- tables])
+
+OVSDB_CHECK_POSITIVE([table with one column],
+ [[parse-table mytable '{"columns": {"name": {"type": "string"}}}']],
+ [[{"columns":{"name":{"type":"string"}}}]])
+
+OVSDB_CHECK_POSITIVE([immutable table with one column],
+ [[parse-table mytable \
+ '{"columns": {"name": {"type": "string"}},
+ "mutable": false}']],
+ [[{"columns":{"name":{"type":"string"}},"mutable":false}]])
+
+OVSDB_CHECK_POSITIVE([table with comment],
+ [[parse-table mytable \
+ '{"columns": {"name": {"type": "string"}},
+ "comment": "description of table"}']],
+ [[{"columns":{"name":{"type":"string"}},"comment":"description of table"}]])
+
+OVSDB_CHECK_NEGATIVE([column names may not begin with _],
+ [[parse-table mytable \
+ '{"columns": {"_column": {"type": "integer"}}}']],
+ [[names beginning with "_" are reserved]],
+ [table])
+
+OVSDB_CHECK_NEGATIVE([table must have at least one column (1)],
+ [[parse-table mytable '{}']],
+ [[Parsing table schema for table mytable failed: Required 'columns' member is missing.]])
+
+OVSDB_CHECK_NEGATIVE([table must have at least one column (2)],
+ [[parse-table mytable '{"columns": {}}']],
+ [[table must have at least one column]])
diff --git a/tests/ovsdb-transaction.at b/tests/ovsdb-transaction.at
new file mode 100644
index 00000000..f0c29d43
--- /dev/null
+++ b/tests/ovsdb-transaction.at
@@ -0,0 +1,384 @@
+AT_BANNER([OVSDB -- transactions])
+
+OVSDB_CHECK_POSITIVE([empty table, empty transaction],
+ [[transact \
+ '["print"]' \
+ '["commit"]' \
+ '["print"]' \
+ '["abort"]' \
+ '["print"]']],
+ [dnl
+print:
+commit:
+print:
+abort:
+print:])
+
+OVSDB_CHECK_POSITIVE([nonempty table, empty transaction],
+ [[transact \
+ '["insert", "1", "2", "3"]' \
+ '["insert", "2", "2", "3"]' \
+ '["print"]' \
+ '["commit"]' \
+ '["print"]' \
+ '["abort"]' \
+ '["print"]']],
+ [dnl
+insert 1 2 3:
+insert 2 2 3:
+print:
+1: i=2, j=3
+2: i=2, j=3
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3])
+
+OVSDB_CHECK_POSITIVE([insert, commit],
+ [[transact \
+ '["insert", "1", "2", "3"]' \
+ '["insert", "2", "2", "3"]' \
+ '["commit"]' \
+ '["print"]' \
+ '["insert", "3", "1", "2"]' \
+ '["print"]' \
+ '["commit"]' \
+ '["print"]']],
+ [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 1 2:
+print:
+1: i=2, j=3
+2: i=2, j=3
+3: i=1, j=2
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+3: i=1, j=2],
+ [transaction])
+
+OVSDB_CHECK_POSITIVE([insert, abort],
+ [[transact \
+ '["insert", "1", "2", "3"]' \
+ '["insert", "2", "2", "3"]' \
+ '["commit"]' \
+ '["print"]' \
+ '["insert", "3", "1", "2"]' \
+ '["print"]' \
+ '["abort"]' \
+ '["print"]']],
+ [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 1 2:
+print:
+1: i=2, j=3
+2: i=2, j=3
+3: i=1, j=2
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+ [transaction])
+
+OVSDB_CHECK_POSITIVE([modify, commit],
+ [[transact \
+ '["insert", "1", "2", "3"]' \
+ '["insert", "2", "2", "3"]' \
+ '["commit"]' \
+ '["print"]' \
+ '["modify", "2", "5", "-1"]' \
+ '["modify", "1", "-1", "4"]' \
+ '["print"]' \
+ '["commit"]' \
+ '["print"]']],
+ [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+modify 2 5 -1:
+modify 1 -1 4:
+print:
+1: i=2, j=4
+2: i=5, j=3
+commit:
+print:
+1: i=2, j=4
+2: i=5, j=3],
+ [transaction])
+
+OVSDB_CHECK_POSITIVE([modify, abort],
+ [[transact \
+ '["insert", "1", "2", "3"]' \
+ '["insert", "2", "2", "3"]' \
+ '["commit"]' \
+ '["print"]' \
+ '["modify", "2", "5", "-1"]' \
+ '["modify", "1", "-1", "4"]' \
+ '["print"]' \
+ '["abort"]' \
+ '["print"]']],
+ [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+modify 2 5 -1:
+modify 1 -1 4:
+print:
+1: i=2, j=4
+2: i=5, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+ [transaction])
+
+OVSDB_CHECK_POSITIVE([delete, commit],
+ [[transact \
+ '["insert", "1", "2", "3"]' \
+ '["insert", "2", "2", "3"]' \
+ '["commit"]' \
+ '["print"]' \
+ '["delete", "1"]' \
+ '["print"]' \
+ '["commit"]' \
+ '["print"]']],
+ [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+delete 1:
+print:
+2: i=2, j=3
+commit:
+print:
+2: i=2, j=3],
+ [transaction])
+
+OVSDB_CHECK_POSITIVE([delete, abort],
+ [[transact \
+ '["insert", "1", "2", "3"]' \
+ '["insert", "2", "2", "3"]' \
+ '["commit"]' \
+ '["print"]' \
+ '["delete", "1"]' \
+ '["print"]' \
+ '["abort"]' \
+ '["print"]']],
+ [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+delete 1:
+print:
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+ [transaction])
+
+OVSDB_CHECK_POSITIVE([modify, delete, commit],
+ [[transact \
+ '["insert", "1", "2", "3"]' \
+ '["insert", "2", "2", "3"]' \
+ '["commit"]' \
+ '["print"]' \
+ '["modify", "1", "5", "6"]' \
+ '["delete", "1"]' \
+ '["print"]' \
+ '["commit"]' \
+ '["print"]']],
+ [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+modify 1 5 6:
+delete 1:
+print:
+2: i=2, j=3
+commit:
+print:
+2: i=2, j=3],
+ [transaction])
+
+OVSDB_CHECK_POSITIVE([modify, delete, abort],
+ [[transact \
+ '["insert", "1", "2", "3"]' \
+ '["insert", "2", "2", "3"]' \
+ '["commit"]' \
+ '["print"]' \
+ '["modify", "1", "5", "6"]' \
+ '["delete", "1"]' \
+ '["print"]' \
+ '["abort"]' \
+ '["print"]']],
+ [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+modify 1 5 6:
+delete 1:
+print:
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+ [transaction])
+
+OVSDB_CHECK_POSITIVE([insert, delete, commit],
+ [[transact \
+ '["insert", "1", "2", "3"]' \
+ '["insert", "2", "2", "3"]' \
+ '["commit"]' \
+ '["print"]' \
+ '["insert", "3", "5", "6"]' \
+ '["delete", "1"]' \
+ '["delete", "3"]' \
+ '["print"]' \
+ '["commit"]' \
+ '["print"]']],
+ [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 5 6:
+delete 1:
+delete 3:
+print:
+2: i=2, j=3
+commit:
+print:
+2: i=2, j=3],
+ [transaction])
+
+OVSDB_CHECK_POSITIVE([insert, delete, abort],
+ [[transact \
+ '["insert", "1", "2", "3"]' \
+ '["insert", "2", "2", "3"]' \
+ '["commit"]' \
+ '["print"]' \
+ '["insert", "3", "5", "6"]' \
+ '["delete", "1"]' \
+ '["delete", "3"]' \
+ '["print"]' \
+ '["abort"]' \
+ '["print"]']],
+ [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 5 6:
+delete 1:
+delete 3:
+print:
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+ [transaction])
+
+
+OVSDB_CHECK_POSITIVE([insert, modify, delete, commit],
+ [[transact \
+ '["insert", "1", "2", "3"]' \
+ '["insert", "2", "2", "3"]' \
+ '["commit"]' \
+ '["print"]' \
+ '["insert", "3", "5", "6"]' \
+ '["delete", "1"]' \
+ '["modify", "3", "7", "8"]' \
+ '["delete", "3"]' \
+ '["print"]' \
+ '["commit"]' \
+ '["print"]']],
+ [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 5 6:
+delete 1:
+modify 3 7 8:
+delete 3:
+print:
+2: i=2, j=3
+commit:
+print:
+2: i=2, j=3],
+ [transaction])
+
+OVSDB_CHECK_POSITIVE([insert, modify, delete, abort],
+ [[transact \
+ '["insert", "1", "2", "3"]' \
+ '["insert", "2", "2", "3"]' \
+ '["commit"]' \
+ '["print"]' \
+ '["insert", "3", "5", "6"]' \
+ '["delete", "1"]' \
+ '["modify", "3", "7", "8"]' \
+ '["delete", "3"]' \
+ '["print"]' \
+ '["abort"]' \
+ '["print"]']],
+ [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 5 6:
+delete 1:
+modify 3 7 8:
+delete 3:
+print:
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+ [transaction])
+
diff --git a/tests/ovsdb-trigger.at b/tests/ovsdb-trigger.at
new file mode 100644
index 00000000..5980d852
--- /dev/null
+++ b/tests/ovsdb-trigger.at
@@ -0,0 +1,173 @@
+AT_BANNER([OVSDB -- triggers])
+
+# This is like OVSDB_CHECK_POSITIVE, except that UUIDs in the output
+# are replaced by markers of the form <N> where N is a number. The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+m4_define([OVSDB_CHECK_TRIGGER],
+ [AT_SETUP([$1])
+ AT_KEYWORDS([ovsdb execute execution trigger positive $4])
+ OVS_CHECK_LCOV([test-ovsdb trigger $2], [0], [stdout], [])
+ AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0], [$3])
+ AT_CLEANUP])
+
+OVSDB_CHECK_TRIGGER([trigger fires immediately],
+ [ORDINAL_SCHEMA [\
+ '[{"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 0, "name": "zero"}},
+ {"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 1, "name": "one"}},
+ {"op": "wait",
+ "timeout": 10,
+ "table": "ordinals",
+ "where": [],
+ "columns": ["name", "number"],
+ "until": "==",
+ "rows": [{"name": "zero", "number": 0},
+ {"name": "one", "number": 1}]},
+ {"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 2, "name": "two"}}]']],
+ [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{},{"uuid":["uuid","<2>"]}]
+]])
+
+OVSDB_CHECK_TRIGGER([trigger times out],
+ [ORDINAL_SCHEMA [\
+ '[{"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 0, "name": "zero"}},
+ {"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 1, "name": "one"}},
+ {"op": "wait",
+ "timeout": 10,
+ "table": "ordinals",
+ "where": [],
+ "columns": ["name", "number"],
+ "until": "==",
+ "rows": [{"name": "zero", "number": 0},
+ {"name": "one", "number": 1},
+ {"name": "two", "number": 2}]}]' \
+ '["advance", 10]']],
+ [[t=0: new trigger 0
+t=10: trigger 0 (delayed): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out after 10 ms","error":"timed out"}]
+]])
+
+OVSDB_CHECK_TRIGGER([trigger fires after delay],
+ [ORDINAL_SCHEMA [\
+ '[{"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 0, "name": "zero"}},
+ {"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 1, "name": "one"}}]' \
+ '["advance", 5]' \
+ '[{"op": "wait",
+ "timeout": 10,
+ "table": "ordinals",
+ "where": [],
+ "columns": ["name", "number"],
+ "until": "==",
+ "rows": [{"name": "zero", "number": 0},
+ {"name": "one", "number": 1},
+ {"name": "two", "number": 2}]}]' \
+ '["advance", 5]' \
+ '[{"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 2, "name": "two"}}]']],
+ [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]
+t=5: new trigger 1
+t=10: trigger 2 (immediate): [{"uuid":["uuid","<2>"]}]
+t=10: trigger 1 (delayed): [{}]
+]])
+
+OVSDB_CHECK_TRIGGER([delayed trigger modifies database],
+ [ORDINAL_SCHEMA [\
+ '[{"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 0, "name": "zero"}},
+ {"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 1, "name": "one"}}]' \
+ '["advance", 5]' \
+ '[{"op": "wait",
+ "timeout": 10,
+ "table": "ordinals",
+ "where": [],
+ "columns": ["name", "number"],
+ "until": "==",
+ "rows": [{"name": "zero", "number": 0},
+ {"name": "one", "number": 1},
+ {"name": "two", "number": 2}]},
+ {"op": "delete",
+ "table": "ordinals",
+ "where": [["number", "<", 2]]}]' \
+ '["advance", 5]' \
+ '[{"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 2, "name": "two"}}]' \
+ '["advance", 5]' \
+ '[{"op": "select",
+ "table": "ordinals",
+ "where": []}]']],
+ [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]
+t=5: new trigger 1
+t=10: trigger 2 (immediate): [{"uuid":["uuid","<2>"]}]
+t=10: trigger 1 (delayed): [{},{"count":2}]
+t=15: trigger 3 (immediate): [{"rows":[{"_uuid":["uuid","<2>"],"_version":["uuid","<3>"],"name":"two","number":2}]}]
+]])
+
+OVSDB_CHECK_TRIGGER([one delayed trigger wakes up another],
+ [ORDINAL_SCHEMA [\
+ '[{"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 0, "name": "zero"}},
+ {"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 1, "name": "one"}}]' \
+ '["advance", 5]' \
+ '[{"op": "wait",
+ "timeout": 10,
+ "table": "ordinals",
+ "where": [],
+ "columns": ["name", "number"],
+ "until": "==",
+ "rows": [{"name": "two", "number": 2}]},
+ {"op": "delete",
+ "table": "ordinals",
+ "where": [["number", "==", 2]]},
+ {"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 3, "name": "three"}}]' \
+ '[{"op": "wait",
+ "timeout": 10,
+ "table": "ordinals",
+ "where": [],
+ "columns": ["name", "number"],
+ "until": "==",
+ "rows": [{"name": "zero", "number": 0},
+ {"name": "one", "number": 1},
+ {"name": "two", "number": 2}]},
+ {"op": "delete",
+ "table": "ordinals",
+ "where": [["number", "<", 2]]}]' \
+ '["advance", 5]' \
+ '[{"op": "insert",
+ "table": "ordinals",
+ "row": {"number": 2, "name": "two"}}]' \
+ '["advance", 5]' \
+ '[{"op": "select",
+ "table": "ordinals",
+ "where": []}]']],
+ [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]
+t=5: new trigger 1
+t=5: new trigger 2
+t=10: trigger 3 (immediate): [{"uuid":["uuid","<2>"]}]
+t=10: trigger 2 (delayed): [{},{"count":2}]
+t=15: trigger 1 (delayed): [{},{"count":1},{"uuid":["uuid","<3>"]}]
+t=15: trigger 4 (immediate): [{"rows":[{"_uuid":["uuid","<3>"],"_version":["uuid","<4>"],"name":"three","number":3}]}]
+]])
+
diff --git a/tests/ovsdb-types.at b/tests/ovsdb-types.at
new file mode 100644
index 00000000..9a92a5ca
--- /dev/null
+++ b/tests/ovsdb-types.at
@@ -0,0 +1,90 @@
+AT_BANNER([OVSDB -- atomic types])
+
+OVSDB_CHECK_POSITIVE([integer],
+ [[parse-atomic-type '["integer"]' ]], ["integer"])
+OVSDB_CHECK_POSITIVE([real],
+ [[parse-atomic-type '["real"]' ]], ["real"])
+OVSDB_CHECK_POSITIVE([boolean],
+ [[parse-atomic-type '["boolean"]' ]], ["boolean"])
+OVSDB_CHECK_POSITIVE([string],
+ [[parse-atomic-type '["string"]' ]], ["string"])
+OVSDB_CHECK_POSITIVE([uuid],
+ [[parse-atomic-type '["uuid"]' ]], ["uuid"])
+OVSDB_CHECK_NEGATIVE([void is not a valid atomic-type],
+ [[parse-atomic-type '["void"]' ]], ["void" is not an atomic-type])
+
+AT_BANNER([OVSDB -- simple types])
+
+OVSDB_CHECK_POSITIVE([simple integer],
+ [[parse-type '["integer"]' ]], ["integer"])
+OVSDB_CHECK_POSITIVE([simple real],
+ [[parse-type '["real"]' ]], ["real"])
+OVSDB_CHECK_POSITIVE([simple boolean],
+ [[parse-type '["boolean"]' ]], ["boolean"])
+OVSDB_CHECK_POSITIVE([simple string],
+ [[parse-type '["string"]' ]], ["string"])
+OVSDB_CHECK_POSITIVE([simple uuid],
+ [[parse-type '["uuid"]' ]], ["uuid"])
+OVSDB_CHECK_POSITIVE([integer in object],
+ [[parse-type '{"key": "integer"}' ]], ["integer"])
+OVSDB_CHECK_POSITIVE([real in object with explicit min and max],
+ [[parse-type '{"key": "real", "min": 1, "max": 1}' ]], ["real"])
+
+OVSDB_CHECK_NEGATIVE([key type is required],
+ [[parse-type '{}' ]], [Required 'key' member is missing.])
+OVSDB_CHECK_NEGATIVE([void is not a valid type],
+ [[parse-type '["void"]' ]], ["void" is not an atomic-type])
+
+AT_BANNER([OVSDB -- set types])
+
+OVSDB_CHECK_POSITIVE([optional boolean],
+ [[parse-type '{"key": "boolean", "min": 0}' ]],
+ [[{"key":"boolean","min":0}]],
+ [set])
+OVSDB_CHECK_POSITIVE([set of 1 to 3 uuids],
+ [[parse-type '{"key": "uuid", "min": 1, "max": 3}' ]],
+ [[{"key":"uuid","max":3}]])
+OVSDB_CHECK_POSITIVE([set of 0 to 3 strings],
+ [[parse-type '{"key": "string", "min": 0, "max": 3}' ]],
+ [[{"key":"string","max":3,"min":0}]])
+OVSDB_CHECK_POSITIVE([set of 0 or more integers],
+ [[parse-type '{"key": "integer", "min": 0, "max": "unlimited"}']],
+ [[{"key":"integer","max":"unlimited","min":0}]])
+OVSDB_CHECK_POSITIVE([set of 10 or more reals],
+ [[parse-type '{"key": "real", "min": 10, "max": "unlimited"}']],
+ [[{"key":"real","max":"unlimited","min":10}]])
+
+OVSDB_CHECK_NEGATIVE([set max cannot be less than min],
+ [[parse-type '{"key": "real", "min": 5, "max": 3}' ]],
+ [ovsdb type fails constraint checks])
+OVSDB_CHECK_NEGATIVE([set max cannot be negative],
+ [[parse-type '{"key": "real", "max": -1}' ]],
+ [bad min or max value])
+OVSDB_CHECK_NEGATIVE([set min cannot be negative],
+ [[parse-type '{"key": "real", "min": -1}' ]],
+ [bad min or max value])
+
+AT_BANNER([OVSDB -- map types])
+
+OVSDB_CHECK_POSITIVE([map of 1 integer to boolean],
+ [[parse-type '{"key": "integer", "value": "boolean"}' ]],
+ [[{"key":"integer","value":"boolean"}]])
+OVSDB_CHECK_POSITIVE([map of 1 boolean to integer, explicit min and max],
+ [[parse-type '{"key": "boolean", "value": "integer", "min": 1, "max": 1}' ]],
+ [[{"key":"boolean","value":"integer"}]])
+OVSDB_CHECK_POSITIVE([map of 2 to 5 uuid to real],
+ [[parse-type '{"key": "uuid", "value": "real", "min": 2, "max": 5}' ]],
+ [[{"key":"uuid","max":5,"min":2,"value":"real"}]])
+OVSDB_CHECK_POSITIVE([map of 0 to 10 string to uuid],
+ [[parse-type '{"key": "string", "value": "uuid", "min": 0, "max": 10}' ]],
+ [[{"key":"string","max":10,"min":0,"value":"uuid"}]])
+OVSDB_CHECK_POSITIVE([map of 10 to 20 real to string],
+ [[parse-type '{"key": "real", "value": "string", "min": 10, "max": 20}' ]],
+ [[{"key":"real","max":20,"min":10,"value":"string"}]])
+OVSDB_CHECK_POSITIVE([map of 20 or more string to real],
+ [[parse-type '{"key": "string", "value": "real", "min": 20, "max": "unlimited"}' ]],
+ [[{"key":"string","max":"unlimited","min":20,"value":"real"}]])
+
+OVSDB_CHECK_NEGATIVE([map key type is required],
+ [[parse-type '{"value": "integer"}' ]],
+ [Required 'key' member is missing.])
diff --git a/tests/ovsdb.at b/tests/ovsdb.at
new file mode 100644
index 00000000..0e0e63b0
--- /dev/null
+++ b/tests/ovsdb.at
@@ -0,0 +1,35 @@
+m4_define([OVSDB_CHECK_POSITIVE],
+ [AT_SETUP([$1])
+ m4_if([$5], [], [],
+ [AT_XFAIL_IF([m4_version_prereq([$5], [false], [true])])])
+ AT_KEYWORDS([ovsdb positive $4])
+ OVS_CHECK_LCOV([test-ovsdb $2], [0], [$3
+], [])
+ AT_CLEANUP])
+
+m4_define([OVSDB_CHECK_NEGATIVE],
+ [AT_SETUP([$1])
+ AT_KEYWORDS([ovsdb negative $4])
+ OVS_CHECK_LCOV([test-ovsdb $2], [1], [], [stderr])
+ m4_assert(m4_len([$3]))
+ AT_CHECK(
+ [if grep -F -e "AS_ESCAPE([$3])" stderr
+ then
+ :
+ else
+ exit 99
+ fi],
+ [0], [ignore], [ignore])
+ AT_CLEANUP])
+
+m4_include([tests/ovsdb-file.at])
+m4_include([tests/ovsdb-types.at])
+m4_include([tests/ovsdb-data.at])
+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-query.at])
+m4_include([tests/ovsdb-transaction.at])
+m4_include([tests/ovsdb-execution.at])
+m4_include([tests/ovsdb-trigger.at])
diff --git a/tests/test-ovsdb.c b/tests/test-ovsdb.c
new file mode 100644
index 00000000..bee1818b
--- /dev/null
+++ b/tests/test-ovsdb.c
@@ -0,0 +1,1229 @@
+/*
+ * 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 <assert.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "command-line.h"
+#include "json.h"
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+#include "ovsdb-types.h"
+#include "ovsdb/column.h"
+#include "ovsdb/condition.h"
+#include "ovsdb/file.h"
+#include "ovsdb/ovsdb.h"
+#include "ovsdb/query.h"
+#include "ovsdb/row.h"
+#include "ovsdb/table.h"
+#include "ovsdb/transaction.h"
+#include "ovsdb/trigger.h"
+#include "poll-loop.h"
+#include "svec.h"
+#include "timeval.h"
+#include "util.h"
+#include "vlog.h"
+
+static struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+ set_program_name(argv[0]);
+ time_init();
+ vlog_init();
+ parse_options(argc, argv);
+ run_command(argc - optind, argv + optind, all_commands);
+ return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+ static struct option long_options[] = {
+ {"verbose", optional_argument, 0, 'v'},
+ {"help", no_argument, 0, 'h'},
+ {0, 0, 0, 0},
+ };
+ char *short_options = long_options_to_short_options(long_options);
+
+ for (;;) {
+ int c = getopt_long(argc, argv, short_options, long_options, NULL);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'h':
+ usage();
+
+ case 'v':
+ vlog_set_verbosity(optarg);
+ break;
+
+ case '?':
+ exit(EXIT_FAILURE);
+
+ default:
+ abort();
+ }
+ }
+ free(short_options);
+}
+
+static void
+usage(void)
+{
+ printf("%s: Open vSwitch database test utility\n"
+ "usage: %s [OPTIONS] COMMAND [ARG...]\n\n"
+ " file-io FILE FLAGS COMMAND...\n"
+ " open FILE with FLAGS, run COMMANDs\n"
+ " parse-atomic-type TYPE\n"
+ " parse TYPE as OVSDB atomic type, and re-serialize\n"
+ " parse-type JSON\n"
+ " parse JSON as OVSDB type, and re-serialize\n"
+ " parse-atoms TYPE ATOM...\n"
+ " parse ATOMs as atoms of given TYPE, and re-serialize\n"
+ " sort-atoms TYPE ATOM...\n"
+ " print ATOMs in sorted order, and re-serialize\n"
+ " parse-data TYPE DATUM...\n"
+ " parse DATUMs as data of given TYPE, and re-serialize\n"
+ " parse-column NAME OBJECT\n"
+ " parse column NAME with info OBJECT, and re-serialize\n"
+ " parse-table NAME OBJECT\n"
+ " parse table NAME with info OBJECT\n"
+ " parse-row TABLE ROW..., and re-serialize\n"
+ " parse each ROW of defined TABLE\n"
+ " compare-row TABLE ROW...\n"
+ " mutually compare all of the ROWs, print those that are equal\n"
+ " parse-conditions TABLE CONDITION...\n"
+ " 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"
+ " query TABLE [ROW,...] [CONDITION,...]\n"
+ " add each ROW to TABLE, then query and print the rows that\n"
+ " satisfy each CONDITION.\n"
+ " query-distinct TABLE [ROW,...] [CONDITION,...] COLUMNS\n"
+ " add each ROW to TABLE, then query and print the rows that\n"
+ " satisfy each CONDITION and have distinct COLUMNS.\n"
+ " transact COMMAND\n"
+ " execute each specified transactional COMMAND:\n"
+ " commit\n"
+ " abort\n"
+ " insert UUID I J\n"
+ " delete UUID\n"
+ " modify UUID I J\n"
+ " print\n"
+ " execute SCHEMA TRANSACTION...\n"
+ " executes each TRANSACTION on an initially empty database\n"
+ " the specified SCHEMA\n"
+ " trigger SCHEMA TRANSACTION...\n"
+ " executes each TRANSACTION on an initially empty database\n"
+ " the specified SCHEMA. A TRANSACTION of the form\n"
+ " [\"advance\", NUMBER] advances NUMBER milliseconds in\n"
+ " simulated time, for causing triggers to time out.\n",
+ program_name, program_name);
+ vlog_usage();
+ printf("\nOther options:\n"
+ " -h, --help display this help message\n");
+ exit(EXIT_SUCCESS);
+}
+
+/* Command helper functions. */
+
+static struct json *
+parse_json(const char *s)
+{
+ struct json *json = json_from_string(s);
+ if (json->type == JSON_STRING) {
+ ovs_fatal(0, "\"%s\": %s", s, json->u.string);
+ }
+ return json;
+}
+
+static struct json *
+unbox_json(struct json *json)
+{
+ if (json->type == JSON_ARRAY && json->u.array.n == 1) {
+ struct json *inner = json->u.array.elems[0];
+ json->u.array.elems[0] = NULL;
+ json_destroy(json);
+ return inner;
+ } else {
+ return json;
+ }
+}
+
+static void
+print_and_free_json(struct json *json)
+{
+ char *string = json_to_string(json, JSSF_SORT);
+ json_destroy(json);
+ puts(string);
+ free(string);
+}
+
+static void
+check_ovsdb_error(struct ovsdb_error *error)
+{
+ if (error) {
+ ovs_fatal(0, "%s", ovsdb_error_to_string(error));
+ }
+}
+
+/* Command implementations. */
+
+static void
+do_file_io(int argc, char *argv[])
+{
+ const char *name = argv[1];
+ char *mode = argv[2];
+
+ struct ovsdb_error *error;
+ struct ovsdb_file *file;
+ char *save_ptr = NULL;
+ const char *token;
+ int flags;
+ int i;
+
+ for (flags = 0, token = strtok_r(mode, " |", &save_ptr); token != NULL;
+ token = strtok_r(NULL, " |", &save_ptr))
+ {
+ if (!strcmp(token, "O_RDONLY")) {
+ flags |= O_RDONLY;
+ } else if (!strcmp(token, "O_RDWR")) {
+ flags |= O_RDWR;
+ } else if (!strcmp(token, "O_TRUNC")) {
+ flags |= O_TRUNC;
+ } else if (!strcmp(token, "O_CREAT")) {
+ flags |= O_CREAT;
+ } else if (!strcmp(token, "O_EXCL")) {
+ flags |= O_EXCL;
+ } else if (!strcmp(token, "O_TRUNC")) {
+ flags |= O_TRUNC;
+ }
+ }
+
+ check_ovsdb_error(ovsdb_file_open(name, flags, &file));
+ printf("%s: open successful\n", name);
+
+ for (i = 3; i < argc; i++) {
+ const char *command = argv[i];
+ if (!strcmp(command, "read")) {
+ struct json *json;
+
+ error = ovsdb_file_read(file, &json);
+ if (!error) {
+ printf("%s: read: ", name);
+ if (json) {
+ print_and_free_json(json);
+ } else {
+ printf("end of file\n");
+ }
+ continue;
+ }
+ } else if (!strncmp(command, "write:", 6)) {
+ struct json *json = parse_json(command + 6);
+ error = ovsdb_file_write(file, json);
+ json_destroy(json);
+ } else if (!strcmp(command, "commit")) {
+ error = ovsdb_file_commit(file);
+ } else {
+ ovs_fatal(0, "unknown file-io command \"%s\"", command);
+ }
+ if (error) {
+ char *s = ovsdb_error_to_string(error);
+ printf("%s: %s failed: %s\n", name, command, s);
+ free(s);
+ } else {
+ printf("%s: %s successful\n", name, command);
+ }
+ }
+
+ ovsdb_file_close(file);
+}
+
+static void
+do_parse_atomic_type(int argc UNUSED, char *argv[])
+{
+ enum ovsdb_atomic_type type;
+ struct json *json;
+
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json));
+ json_destroy(json);
+ print_and_free_json(ovsdb_atomic_type_to_json(type));
+}
+
+static void
+do_parse_type(int argc UNUSED, char *argv[])
+{
+ struct ovsdb_type type;
+ struct json *json;
+
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_type_from_json(&type, json));
+ json_destroy(json);
+ print_and_free_json(ovsdb_type_to_json(&type));
+}
+
+static void
+do_parse_atoms(int argc, char *argv[])
+{
+ enum ovsdb_atomic_type type;
+ struct json *json;
+ int i;
+
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json));
+ json_destroy(json);
+
+ for (i = 2; i < argc; i++) {
+ union ovsdb_atom atom;
+
+ json = unbox_json(parse_json(argv[i]));
+ check_ovsdb_error(ovsdb_atom_from_json(&atom, type, json, NULL));
+ json_destroy(json);
+
+ print_and_free_json(ovsdb_atom_to_json(&atom, type));
+
+ ovsdb_atom_destroy(&atom, type);
+ }
+}
+
+static void
+do_parse_data(int argc, char *argv[])
+{
+ struct ovsdb_type type;
+ struct json *json;
+ int i;
+
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_type_from_json(&type, json));
+ json_destroy(json);
+
+ for (i = 2; i < argc; i++) {
+ struct ovsdb_datum datum;
+
+ json = unbox_json(parse_json(argv[i]));
+ check_ovsdb_error(ovsdb_datum_from_json(&datum, &type, json, NULL));
+ json_destroy(json);
+
+ print_and_free_json(ovsdb_datum_to_json(&datum, &type));
+
+ ovsdb_datum_destroy(&datum, &type);
+ }
+}
+
+static enum ovsdb_atomic_type compare_atoms_atomic_type;
+
+static int
+compare_atoms(const void *a_, const void *b_)
+{
+ const union ovsdb_atom *a = a_;
+ const union ovsdb_atom *b = b_;
+
+ return ovsdb_atom_compare_3way(a, b, compare_atoms_atomic_type);
+}
+
+static void
+do_sort_atoms(int argc UNUSED, char *argv[])
+{
+ enum ovsdb_atomic_type type;
+ union ovsdb_atom *atoms;
+ struct json *json, **json_atoms;
+ size_t n_atoms;
+ int i;
+
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json));
+ json_destroy(json);
+
+ json = unbox_json(parse_json(argv[2]));
+ if (json->type != JSON_ARRAY) {
+ ovs_fatal(0, "second argument must be array");
+ }
+
+ /* Convert JSON atoms to internal representation. */
+ n_atoms = json->u.array.n;
+ atoms = xmalloc(n_atoms * sizeof *atoms);
+ for (i = 0; i < n_atoms; i++) {
+ check_ovsdb_error(ovsdb_atom_from_json(&atoms[i], type,
+ json->u.array.elems[i], NULL));
+ }
+ json_destroy(json);
+
+ /* Sort atoms. */
+ compare_atoms_atomic_type = type;
+ qsort(atoms, n_atoms, sizeof *atoms, compare_atoms);
+
+ /* Convert internal representation back to JSON. */
+ json_atoms = xmalloc(n_atoms * sizeof *json_atoms);
+ for (i = 0; i < n_atoms; i++) {
+ json_atoms[i] = ovsdb_atom_to_json(&atoms[i], type);
+ ovsdb_atom_destroy(&atoms[i], type);
+ }
+ print_and_free_json(json_array_create(json_atoms, n_atoms));
+}
+
+static void
+do_parse_column(int argc UNUSED, char *argv[])
+{
+ struct ovsdb_column *column;
+ struct json *json;
+
+ json = parse_json(argv[2]);
+ check_ovsdb_error(ovsdb_column_from_json(json, argv[1], &column));
+ json_destroy(json);
+ print_and_free_json(ovsdb_column_to_json(column));
+ ovsdb_column_destroy(column);
+}
+
+static void
+do_parse_table(int argc UNUSED, char *argv[])
+{
+ struct ovsdb_table_schema *ts;
+ struct json *json;
+
+ json = parse_json(argv[2]);
+ check_ovsdb_error(ovsdb_table_schema_from_json(json, argv[1], &ts));
+ json_destroy(json);
+ print_and_free_json(ovsdb_table_schema_to_json(ts));
+ ovsdb_table_schema_destroy(ts);
+}
+
+static void
+do_parse_rows(int argc, char *argv[])
+{
+ struct ovsdb_column_set all_columns;
+ struct ovsdb_table_schema *ts;
+ struct ovsdb_table *table;
+ struct json *json;
+ int i;
+
+ 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);
+ ovsdb_column_set_init(&all_columns);
+ ovsdb_column_set_add_all(&all_columns, table);
+
+ for (i = 2; i < argc; i++) {
+ struct ovsdb_column_set columns;
+ struct ovsdb_row *row;
+
+ ovsdb_column_set_init(&columns);
+ row = ovsdb_row_create(table);
+
+ json = unbox_json(parse_json(argv[i]));
+ check_ovsdb_error(ovsdb_row_from_json(row, json, NULL, &columns));
+ json_destroy(json);
+
+ print_and_free_json(ovsdb_row_to_json(row, &all_columns));
+
+ if (columns.n_columns) {
+ struct svec names;
+ size_t j;
+ char *s;
+
+ svec_init(&names);
+ for (j = 0; j < columns.n_columns; j++) {
+ svec_add(&names, columns.columns[j]->name);
+ }
+ svec_sort(&names);
+ s = svec_join(&names, ", ", "");
+ puts(s);
+ free(s);
+ svec_destroy(&names);
+ } else {
+ printf("<none>\n");
+ }
+
+ ovsdb_column_set_destroy(&columns);
+ ovsdb_row_destroy(row);
+ }
+
+ ovsdb_column_set_destroy(&all_columns);
+ ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
+static void
+do_compare_rows(int argc, char *argv[])
+{
+ struct ovsdb_column_set all_columns;
+ struct ovsdb_table_schema *ts;
+ struct ovsdb_table *table;
+ struct ovsdb_row **rows;
+ struct json *json;
+ char **names;
+ int n_rows;
+ int i, j;
+
+ 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);
+ ovsdb_column_set_init(&all_columns);
+ ovsdb_column_set_add_all(&all_columns, table);
+
+ n_rows = argc - 2;
+ rows = xmalloc(sizeof *rows * n_rows);
+ names = xmalloc(sizeof *names * n_rows);
+ for (i = 0; i < n_rows; i++) {
+ rows[i] = ovsdb_row_create(table);
+
+ json = parse_json(argv[i + 2]);
+ if (json->type != JSON_ARRAY || json->u.array.n != 2
+ || json->u.array.elems[0]->type != JSON_STRING) {
+ ovs_fatal(0, "\"%s\" does not have expected form "
+ "[\"name\", {data}]", argv[i]);
+ }
+ names[i] = xstrdup(json->u.array.elems[0]->u.string);
+ check_ovsdb_error(ovsdb_row_from_json(rows[i], json->u.array.elems[1],
+ NULL, NULL));
+ json_destroy(json);
+ }
+ for (i = 0; i < n_rows; i++) {
+ uint32_t i_hash = ovsdb_row_hash_columns(rows[i], &all_columns, 0);
+ for (j = i + 1; j < n_rows; j++) {
+ uint32_t j_hash = ovsdb_row_hash_columns(rows[j], &all_columns, 0);
+ if (ovsdb_row_equal_columns(rows[i], rows[j], &all_columns)) {
+ printf("%s == %s\n", names[i], names[j]);
+ if (i_hash != j_hash) {
+ printf("but hash(%s) != hash(%s)\n", names[i], names[j]);
+ abort();
+ }
+ } else if (i_hash == j_hash) {
+ printf("hash(%s) == hash(%s)\n", names[i], names[j]);
+ }
+ }
+ }
+ free(rows);
+ free(names);
+
+ ovsdb_column_set_destroy(&all_columns);
+ ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
+static void
+do_parse_conditions(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_condition cnd;
+ struct ovsdb_error *error;
+
+ json = parse_json(argv[i]);
+ error = ovsdb_condition_from_json(ts, json, NULL, &cnd);
+ if (!error) {
+ print_and_free_json(ovsdb_condition_to_json(&cnd));
+ } 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_condition_destroy(&cnd);
+ }
+ ovsdb_table_schema_destroy(ts);
+
+ exit(exit_code);
+}
+
+static void
+do_evaluate_conditions(int argc UNUSED, char *argv[])
+{
+ struct ovsdb_table_schema *ts;
+ struct ovsdb_table *table;
+ struct ovsdb_condition *conditions;
+ size_t n_conditions;
+ 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 conditions. */
+ json = parse_json(argv[2]);
+ if (json->type != JSON_ARRAY) {
+ ovs_fatal(0, "CONDITION argument is not JSON array");
+ }
+ n_conditions = json->u.array.n;
+ conditions = xmalloc(n_conditions * sizeof *conditions);
+ for (i = 0; i < n_conditions; i++) {
+ check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i],
+ NULL, &conditions[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_conditions; i++) {
+ printf("condition %2d:", i);
+ for (j = 0; j < n_rows; j++) {
+ bool result = ovsdb_condition_evaluate(rows[j], &conditions[i]);
+ if (j % 5 == 0) {
+ putchar(' ');
+ }
+ putchar(result ? 'T' : '-');
+ }
+ printf("\n");
+ }
+
+ for (i = 0; i < n_conditions; i++) {
+ ovsdb_condition_destroy(&conditions[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;
+ size_t n_rows;
+};
+
+static bool
+do_query_cb(const struct ovsdb_row *row, void *cbdata_)
+{
+ struct do_query_cbdata *cbdata = cbdata_;
+ size_t i;
+
+ for (i = 0; i < cbdata->n_rows; i++) {
+ if (uuid_equals(ovsdb_row_get_uuid(row), &cbdata->row_uuids[i])) {
+ cbdata->counts[i]++;
+ }
+ }
+
+ return true;
+}
+
+static void
+do_query(int argc UNUSED, char *argv[])
+{
+ struct do_query_cbdata cbdata;
+ struct ovsdb_table_schema *ts;
+ struct ovsdb_table *table;
+ struct json *json;
+ int exit_code = 0;
+ size_t i;
+
+ /* 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 rows, add to table. */
+ json = parse_json(argv[2]);
+ if (json->type != JSON_ARRAY) {
+ ovs_fatal(0, "ROW argument is not JSON array");
+ }
+ cbdata.n_rows = json->u.array.n;
+ cbdata.row_uuids = xmalloc(cbdata.n_rows * sizeof *cbdata.row_uuids);
+ cbdata.counts = xmalloc(cbdata.n_rows * sizeof *cbdata.counts);
+ for (i = 0; i < cbdata.n_rows; i++) {
+ struct ovsdb_row *row = ovsdb_row_create(table);
+ uuid_generate(ovsdb_row_get_uuid_rw(row));
+ check_ovsdb_error(ovsdb_row_from_json(row, json->u.array.elems[i],
+ NULL, NULL));
+ if (ovsdb_table_get_row(table, ovsdb_row_get_uuid(row))) {
+ ovs_fatal(0, "duplicate UUID "UUID_FMT" in table",
+ UUID_ARGS(ovsdb_row_get_uuid(row)));
+ }
+ cbdata.row_uuids[i] = *ovsdb_row_get_uuid(row);
+ ovsdb_table_put_row(table, row);
+ }
+ json_destroy(json);
+
+ /* Parse conditions and execute queries. */
+ json = parse_json(argv[3]);
+ if (json->type != JSON_ARRAY) {
+ ovs_fatal(0, "CONDITION argument is not JSON array");
+ }
+ for (i = 0; i < json->u.array.n; i++) {
+ struct ovsdb_condition cnd;
+ size_t j;
+
+ check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i],
+ NULL, &cnd));
+
+ memset(cbdata.counts, 0, cbdata.n_rows * sizeof *cbdata.counts);
+ ovsdb_query(table, &cnd, do_query_cb, &cbdata);
+
+ printf("query %2d:", i);
+ for (j = 0; j < cbdata.n_rows; j++) {
+ if (j % 5 == 0) {
+ putchar(' ');
+ }
+ if (cbdata.counts[j]) {
+ printf("%d", cbdata.counts[j]);
+ if (cbdata.counts[j] > 1) {
+ /* Dup! */
+ exit_code = 1;
+ }
+ } else {
+ putchar('-');
+ }
+ }
+ putchar('\n');
+
+ ovsdb_condition_destroy(&cnd);
+ }
+ json_destroy(json);
+
+ ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+
+ exit(exit_code);
+}
+
+struct do_query_distinct_class {
+ struct ovsdb_row *example;
+ int count;
+};
+
+struct do_query_distinct_row {
+ struct uuid uuid;
+ struct do_query_distinct_class *class;
+};
+
+static void
+do_query_distinct(int argc UNUSED, char *argv[])
+{
+ struct ovsdb_column_set columns;
+ struct ovsdb_table_schema *ts;
+ struct ovsdb_table *table;
+ struct do_query_distinct_row *rows;
+ size_t n_rows;
+ struct do_query_distinct_class *classes;
+ size_t n_classes;
+ struct json *json;
+ int exit_code = 0;
+ size_t i, j, k;
+
+ /* 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 column set. */
+ json = parse_json(argv[4]);
+ ovsdb_column_set_from_json(json, table, &columns);
+ json_destroy(json);
+
+ /* Parse rows, add to table. */
+ json = parse_json(argv[2]);
+ 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);
+ classes = xmalloc(n_rows * sizeof *classes);
+ n_classes = 0;
+ for (i = 0; i < n_rows; i++) {
+ struct ovsdb_row *row;
+ size_t j;
+
+ /* Parse row. */
+ row = ovsdb_row_create(table);
+ uuid_generate(ovsdb_row_get_uuid_rw(row));
+ check_ovsdb_error(ovsdb_row_from_json(row, json->u.array.elems[i],
+ NULL, NULL));
+
+ /* Initialize row and find equivalence class. */
+ rows[i].uuid = *ovsdb_row_get_uuid(row);
+ rows[i].class = NULL;
+ for (j = 0; j < n_classes; j++) {
+ if (ovsdb_row_equal_columns(row, classes[j].example, &columns)) {
+ rows[i].class = &classes[j];
+ break;
+ }
+ }
+ if (!rows[i].class) {
+ rows[i].class = &classes[n_classes];
+ classes[n_classes].example = ovsdb_row_clone(row);
+ n_classes++;
+ }
+
+ /* Add row to table. */
+ if (ovsdb_table_get_row(table, ovsdb_row_get_uuid(row))) {
+ ovs_fatal(0, "duplicate UUID "UUID_FMT" in table",
+ UUID_ARGS(ovsdb_row_get_uuid(row)));
+ }
+ ovsdb_table_put_row(table, row);
+
+ }
+ json_destroy(json);
+
+ /* Parse conditions and execute queries. */
+ json = parse_json(argv[3]);
+ if (json->type != JSON_ARRAY) {
+ ovs_fatal(0, "CONDITION argument is not JSON array");
+ }
+ for (i = 0; i < json->u.array.n; i++) {
+ struct ovsdb_row_set results;
+ struct ovsdb_condition cnd;
+
+ check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i],
+ NULL, &cnd));
+
+ for (j = 0; j < n_classes; j++) {
+ classes[j].count = 0;
+ }
+ ovsdb_row_set_init(&results);
+ ovsdb_query_distinct(table, &cnd, &columns, &results);
+ for (j = 0; j < results.n_rows; j++) {
+ for (k = 0; k < n_rows; k++) {
+ if (uuid_equals(ovsdb_row_get_uuid(results.rows[j]),
+ &rows[k].uuid)) {
+ rows[k].class->count++;
+ }
+ }
+ }
+ ovsdb_row_set_destroy(&results);
+
+ printf("query %2d:", i);
+ for (j = 0; j < n_rows; j++) {
+ int count = rows[j].class->count;
+
+ if (j % 5 == 0) {
+ putchar(' ');
+ }
+ if (count > 1) {
+ /* Dup! */
+ printf("%d", count);
+ exit_code = 1;
+ } else if (count == 1) {
+ putchar("abcdefghijklmnopqrstuvwxyz"[rows[j].class - classes]);
+ } else {
+ putchar('-');
+ }
+ }
+ putchar('\n');
+
+ ovsdb_condition_destroy(&cnd);
+ }
+ json_destroy(json);
+
+ ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+
+ exit(exit_code);
+}
+
+static void
+do_execute(int argc UNUSED, char *argv[])
+{
+ struct ovsdb_schema *schema;
+ struct json *json;
+ struct ovsdb *db;
+ int i;
+
+ /* Create database. */
+ json = parse_json(argv[1]);
+ check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+ json_destroy(json);
+ db = ovsdb_create(NULL, schema);
+
+ for (i = 2; i < argc; i++) {
+ struct json *params, *result;
+ char *s;
+
+ params = parse_json(argv[i]);
+ result = ovsdb_execute(db, params, 0, NULL);
+ s = json_to_string(result, JSSF_SORT);
+ printf("%s\n", s);
+ json_destroy(params);
+ json_destroy(result);
+ }
+
+ ovsdb_destroy(db);
+}
+
+struct test_trigger {
+ struct ovsdb_trigger trigger;
+ int number;
+};
+
+static void
+do_trigger_dump(struct test_trigger *t, long long int now, const char *title)
+{
+ struct json *result;
+ char *s;
+
+ result = ovsdb_trigger_steal_result(&t->trigger);
+ s = json_to_string(result, JSSF_SORT);
+ printf("t=%lld: trigger %d (%s): %s\n", now, t->number, title, s);
+ json_destroy(result);
+ ovsdb_trigger_destroy(&t->trigger);
+ free(t);
+}
+
+static void
+do_trigger(int argc UNUSED, char *argv[])
+{
+ struct ovsdb_schema *schema;
+ struct list completions;
+ struct json *json;
+ struct ovsdb *db;
+ long long int now;
+ int number;
+ int i;
+
+ /* Create database. */
+ json = parse_json(argv[1]);
+ check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+ json_destroy(json);
+ db = ovsdb_create(NULL, schema);
+
+ list_init(&completions);
+ now = 0;
+ number = 0;
+ for (i = 2; i < argc; i++) {
+ struct json *params = parse_json(argv[i]);
+ if (params->type == JSON_ARRAY
+ && json_array(params)->n == 2
+ && json_array(params)->elems[0]->type == JSON_STRING
+ && !strcmp(json_string(json_array(params)->elems[0]), "advance")
+ && json_array(params)->elems[1]->type == JSON_INTEGER) {
+ now += json_integer(json_array(params)->elems[1]);
+ json_destroy(params);
+ } else {
+ struct test_trigger *t = xmalloc(sizeof *t);
+ ovsdb_trigger_init(db, &t->trigger, params, &completions, now);
+ t->number = number++;
+ if (ovsdb_trigger_is_complete(&t->trigger)) {
+ do_trigger_dump(t, now, "immediate");
+ } else {
+ printf("t=%lld: new trigger %d\n", now, t->number);
+ }
+ }
+
+ ovsdb_trigger_run(db, now);
+ while (!list_is_empty(&completions)) {
+ do_trigger_dump(CONTAINER_OF(list_pop_front(&completions),
+ struct test_trigger, trigger.node),
+ now, "delayed");
+ }
+
+ ovsdb_trigger_wait(db, now);
+ poll_immediate_wake();
+ poll_block();
+ }
+
+ ovsdb_destroy(db);
+}
+
+static void
+do_help(int argc UNUSED, char *argv[] UNUSED)
+{
+ usage();
+}
+
+/* "transact" command. */
+
+static struct ovsdb *do_transact_db;
+static struct ovsdb_txn *do_transact_txn;
+static struct ovsdb_table *do_transact_table;
+
+static void
+do_transact_commit(int argc UNUSED, char *argv[] UNUSED)
+{
+ ovsdb_txn_commit(do_transact_txn);
+ do_transact_txn = NULL;
+}
+
+static void
+do_transact_abort(int argc UNUSED, char *argv[] UNUSED)
+{
+ ovsdb_txn_abort(do_transact_txn);
+ do_transact_txn = NULL;
+}
+
+static void
+uuid_from_integer(int integer, struct uuid *uuid)
+{
+ uuid_zero(uuid);
+ uuid->parts[3] = integer;
+}
+
+static const struct ovsdb_row *
+do_transact_find_row(const char *uuid_string)
+{
+ const struct ovsdb_row *row;
+ struct uuid uuid;
+
+ uuid_from_integer(atoi(uuid_string), &uuid);
+ row = ovsdb_table_get_row(do_transact_table, &uuid);
+ if (!row) {
+ ovs_fatal(0, "table does not contain row with UUID "UUID_FMT,
+ UUID_ARGS(&uuid));
+ }
+ return row;
+}
+
+static void
+do_transact_set_integer(struct ovsdb_row *row, const char *column_name,
+ int integer)
+{
+ if (integer != -1) {
+ const struct ovsdb_column *column;
+
+ column = ovsdb_table_schema_get_column(do_transact_table->schema,
+ column_name);
+ row->fields[column->index].keys[0].integer = integer;
+ }
+}
+
+static int
+do_transact_get_integer(const struct ovsdb_row *row, const char *column_name)
+{
+ const struct ovsdb_column *column;
+
+ column = ovsdb_table_schema_get_column(do_transact_table->schema,
+ column_name);
+ return row->fields[column->index].keys[0].integer;
+}
+
+static void
+do_transact_set_i_j(struct ovsdb_row *row,
+ const char *i_string, const char *j_string)
+{
+ do_transact_set_integer(row, "i", atoi(i_string));
+ do_transact_set_integer(row, "j", atoi(j_string));
+}
+
+static void
+do_transact_insert(int argc UNUSED, char *argv[] UNUSED)
+{
+ struct ovsdb_row *row;
+ struct uuid *uuid;
+
+ row = ovsdb_row_create(do_transact_table);
+
+ /* Set UUID. */
+ uuid = ovsdb_row_get_uuid_rw(row);
+ uuid_from_integer(atoi(argv[1]), uuid);
+ if (ovsdb_table_get_row(do_transact_table, uuid)) {
+ ovs_fatal(0, "table already contains row with UUID "UUID_FMT,
+ UUID_ARGS(uuid));
+ }
+
+ do_transact_set_i_j(row, argv[2], argv[3]);
+
+ /* Insert row. */
+ ovsdb_txn_row_insert(do_transact_txn, row);
+}
+
+static void
+do_transact_delete(int argc UNUSED, char *argv[] UNUSED)
+{
+ const struct ovsdb_row *row = do_transact_find_row(argv[1]);
+ ovsdb_txn_row_delete(do_transact_txn, row);
+}
+
+static void
+do_transact_modify(int argc UNUSED, char *argv[] UNUSED)
+{
+ const struct ovsdb_row *row_ro;
+ struct ovsdb_row *row_rw;
+
+ row_ro = do_transact_find_row(argv[1]);
+ row_rw = ovsdb_txn_row_modify(do_transact_txn, row_ro);
+ do_transact_set_i_j(row_rw, argv[2], argv[3]);
+}
+
+static int
+compare_rows_by_uuid(const void *a_, const void *b_)
+{
+ struct ovsdb_row *const *ap = a_;
+ struct ovsdb_row *const *bp = b_;
+
+ return uuid_compare_3way(ovsdb_row_get_uuid(*ap), ovsdb_row_get_uuid(*bp));
+}
+
+static void
+do_transact_print(int argc UNUSED, char *argv[] UNUSED)
+{
+ const struct ovsdb_row **rows;
+ const struct ovsdb_row *row;
+ size_t n_rows;
+ size_t i;
+
+ n_rows = hmap_count(&do_transact_table->rows);
+ rows = xmalloc(n_rows * sizeof *rows);
+ i = 0;
+ HMAP_FOR_EACH (row, struct ovsdb_row, hmap_node,
+ &do_transact_table->rows) {
+ rows[i++] = row;
+ }
+ assert(i == n_rows);
+
+ qsort(rows, n_rows, sizeof *rows, compare_rows_by_uuid);
+
+ for (i = 0; i < n_rows; i++) {
+ printf("\n%"PRId32": i=%d, j=%d",
+ ovsdb_row_get_uuid(rows[i])->parts[3],
+ do_transact_get_integer(rows[i], "i"),
+ do_transact_get_integer(rows[i], "j"));
+ }
+
+ free(rows);
+}
+
+static void
+do_transact(int argc, char *argv[])
+{
+ static const struct command do_transact_commands[] = {
+ { "commit", 0, 0, do_transact_commit },
+ { "abort", 0, 0, do_transact_abort },
+ { "insert", 2, 3, do_transact_insert },
+ { "delete", 1, 1, do_transact_delete },
+ { "modify", 2, 3, do_transact_modify },
+ { "print", 0, 0, do_transact_print },
+ { NULL, 0, 0, NULL },
+ };
+
+ struct ovsdb_schema *schema;
+ struct json *json;
+ int i;
+
+ /* Create table. */
+ json = parse_json("{\"name\": \"testdb\", "
+ " \"tables\": "
+ " {\"mytable\": "
+ " {\"columns\": "
+ " {\"i\": {\"type\": \"integer\"}, "
+ " \"j\": {\"type\": \"integer\"}}}}}");
+ check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+ json_destroy(json);
+ do_transact_db = ovsdb_create(NULL, schema);
+ do_transact_table = ovsdb_get_table(do_transact_db, "mytable");
+ assert(do_transact_table != NULL);
+
+ for (i = 1; i < argc; i++) {
+ struct json *command;
+ size_t n_args;
+ char **args;
+ int j;
+
+ command = parse_json(argv[i]);
+ if (command->type != JSON_ARRAY) {
+ ovs_fatal(0, "transaction %d must be JSON array "
+ "with at least 1 element", i);
+ }
+
+ n_args = command->u.array.n;
+ args = xmalloc((n_args + 1) * sizeof *args);
+ for (j = 0; j < n_args; j++) {
+ struct json *s = command->u.array.elems[j];
+ if (s->type != JSON_STRING) {
+ ovs_fatal(0, "transaction %d argument %d must be JSON string",
+ i, j);
+ }
+ args[j] = xstrdup(json_string(s));
+ }
+ args[n_args] = NULL;
+
+ if (!do_transact_txn) {
+ do_transact_txn = ovsdb_txn_create(do_transact_db);
+ }
+
+ for (j = 0; j < n_args; j++) {
+ if (j) {
+ putchar(' ');
+ }
+ fputs(args[j], stdout);
+ }
+ fputs(":", stdout);
+ run_command(n_args, args, do_transact_commands);
+ putchar('\n');
+
+ for (j = 0; j < n_args; j++) {
+ free(args[j]);
+ }
+ free(args);
+ json_destroy(command);
+ }
+ ovsdb_txn_abort(do_transact_txn);
+ ovsdb_destroy(do_transact_db); /* Also destroys 'schema'. */
+}
+
+static struct command all_commands[] = {
+ { "file-io", 2, INT_MAX, do_file_io },
+ { "parse-atomic-type", 1, 1, do_parse_atomic_type },
+ { "parse-type", 1, 1, do_parse_type },
+ { "parse-atoms", 2, INT_MAX, do_parse_atoms },
+ { "parse-data", 2, INT_MAX, do_parse_data },
+ { "sort-atoms", 2, 2, do_sort_atoms },
+ { "parse-column", 2, 2, do_parse_column },
+ { "parse-table", 2, 2, do_parse_table },
+ { "parse-rows", 2, INT_MAX, do_parse_rows },
+ { "compare-rows", 2, INT_MAX, do_compare_rows },
+ { "parse-conditions", 2, INT_MAX, do_parse_conditions },
+ { "evaluate-conditions", 3, 3, do_evaluate_conditions },
+ { "query", 3, 3, do_query },
+ { "query-distinct", 4, 4, do_query_distinct },
+ { "transact", 1, INT_MAX, do_transact },
+ { "execute", 2, INT_MAX, do_execute },
+ { "trigger", 2, INT_MAX, do_trigger },
+ { "help", 0, INT_MAX, do_help },
+ { NULL, 0, 0, NULL },
+};
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 781b6a6c..9b47b8fb 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -16,6 +16,7 @@ limitations under the License.])
AT_TESTED([ovs-vswitchd])
AT_TESTED([ovs-vsctl])
+AT_TESTED([perl])
m4_include([tests/lcov-pre.at])
m4_include([tests/library.at])
@@ -26,6 +27,7 @@ m4_include([tests/json.at])
m4_include([tests/jsonrpc.at])
m4_include([tests/timeval.at])
m4_include([tests/lockfile.at])
+m4_include([tests/ovsdb.at])
m4_include([tests/stp.at])
m4_include([tests/ovs-vsctl.at])
m4_include([tests/lcov-post.at])
diff --git a/tests/uuidfilt.pl b/tests/uuidfilt.pl
new file mode 100755
index 00000000..6f003a52
--- /dev/null
+++ b/tests/uuidfilt.pl
@@ -0,0 +1,21 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+
+our %uuids;
+our $n_uuids = 0;
+sub lookup_uuid {
+ my ($uuid) = @_;
+ if (!exists($uuids{$uuid})) {
+ $uuids{$uuid} = $n_uuids++;
+ }
+ return "<$uuids{$uuid}>";
+}
+
+my $u = '[0-9a-fA-F]';
+my $uuid_re = "${u}{8}-${u}{4}-${u}{4}-${u}{4}-${u}{12}";
+while (<>) {
+ s/($uuid_re)/lookup_uuid($1)/eg;
+ print $_;
+}