From: Sebastian Harl Date: Mon, 28 Jul 2014 20:25:04 +0000 (+0200) Subject: store: Introduced the concept of query filters. X-Git-Tag: sysdb-0.3.0~22 X-Git-Url: https://git.tokkee.org/?p=sysdb.git;a=commitdiff_plain;h=88281ca69be18cdd05acb7fff580f40c091e49f5 store: Introduced the concept of query filters. A filter may be used to preselect objects when evaluating a query. Any object that's used during the evaluation of a matcher will be compared against the filter. Only matching objects will be considered further. --- diff --git a/src/core/store-private.h b/src/core/store-private.h index 30091a7..703bc55 100644 --- a/src/core/store-private.h +++ b/src/core/store-private.h @@ -95,8 +95,10 @@ typedef struct { * conditionals */ -/* compares a host using the specified conditional */ -typedef int (*cmp_cb)(sdb_host_t *, sdb_store_cond_t *); +/* compares a host using the specified conditional and taking the specified + * filter into account */ +typedef int (*cmp_cb)(sdb_host_t *, sdb_store_cond_t *, + sdb_store_matcher_t *); struct sdb_store_cond { sdb_object_t super; diff --git a/src/core/store_lookup.c b/src/core/store_lookup.c index 758ddd0..ac0f2e8 100644 --- a/src/core/store_lookup.c +++ b/src/core/store_lookup.c @@ -55,6 +55,7 @@ typedef struct { sdb_store_matcher_t *m; + sdb_store_matcher_t *filter; sdb_store_lookup_cb cb; void *user_data; } scan_iter_data_t; @@ -68,13 +69,13 @@ scan_iter(sdb_store_obj_t *obj, void *user_data) { scan_iter_data_t *d = user_data; - if (sdb_store_matcher_matches(d->m, obj)) + if (sdb_store_matcher_matches(d->m, obj, d->filter)) return d->cb(obj, d->user_data); return 0; } /* scan_iter */ static sdb_attribute_t * -attr_get(sdb_host_t *host, const char *name) +attr_get(sdb_host_t *host, const char *name, sdb_store_matcher_t *filter) { sdb_avltree_iter_t *iter = NULL; sdb_attribute_t *attr = NULL; @@ -91,6 +92,10 @@ attr_get(sdb_host_t *host, const char *name) break; } sdb_avltree_iter_destroy(iter); + + if (filter && (! sdb_store_matcher_matches(filter, STORE_OBJ(attr), + NULL))) + return NULL; return attr; } /* attr_get */ @@ -99,7 +104,8 @@ attr_get(sdb_host_t *host, const char *name) */ static int -attr_cmp(sdb_host_t *host, sdb_store_cond_t *cond) +attr_cmp(sdb_host_t *host, sdb_store_cond_t *cond, + sdb_store_matcher_t *filter) { sdb_attribute_t *attr; sdb_data_t value = SDB_DATA_INIT; @@ -108,7 +114,7 @@ attr_cmp(sdb_host_t *host, sdb_store_cond_t *cond) if (sdb_store_expr_eval(ATTR_C(cond)->expr, &value)) return INT_MAX; - attr = attr_get(host, ATTR_C(cond)->name); + attr = attr_get(host, ATTR_C(cond)->name, filter); if (! attr) status = INT_MAX; else if (attr->value.type != value.type) @@ -141,34 +147,39 @@ match_string(string_matcher_t *m, const char *name) } /* match_string */ static int -match_logical(sdb_store_matcher_t *m, sdb_host_t *host) +match_logical(sdb_store_matcher_t *m, sdb_host_t *host, + sdb_store_matcher_t *filter) { int status; assert((m->type == MATCHER_AND) || (m->type == MATCHER_OR)); assert(OP_M(m)->left && OP_M(m)->right); - status = sdb_store_matcher_matches(OP_M(m)->left, STORE_OBJ(host)); + status = sdb_store_matcher_matches(OP_M(m)->left, STORE_OBJ(host), + filter); + /* lazy evaluation */ if ((! status) && (m->type == MATCHER_AND)) return status; else if (status && (m->type == MATCHER_OR)) return status; - return sdb_store_matcher_matches(OP_M(m)->right, STORE_OBJ(host)); + return sdb_store_matcher_matches(OP_M(m)->right, STORE_OBJ(host), filter); } /* match_logical */ static int -match_unary(sdb_store_matcher_t *m, sdb_host_t *host) +match_unary(sdb_store_matcher_t *m, sdb_host_t *host, + sdb_store_matcher_t *filter) { assert(m->type == MATCHER_NOT); assert(UOP_M(m)->op); - return !sdb_store_matcher_matches(UOP_M(m)->op, STORE_OBJ(host)); + return !sdb_store_matcher_matches(UOP_M(m)->op, STORE_OBJ(host), filter); } /* match_unary */ static int -match_name(sdb_store_matcher_t *m, sdb_host_t *host) +match_name(sdb_store_matcher_t *m, sdb_host_t *host, + sdb_store_matcher_t *filter) { sdb_avltree_iter_t *iter = NULL; int status = 0; @@ -189,6 +200,9 @@ match_name(sdb_store_matcher_t *m, sdb_host_t *host) while (sdb_avltree_iter_has_next(iter)) { sdb_object_t *child = sdb_avltree_iter_get_next(iter); + if (filter && (! sdb_store_matcher_matches(filter, STORE_OBJ(child), + NULL))) + continue; if (match_string(&NAME_M(m)->name, child->name)) { status = 1; break; @@ -199,14 +213,15 @@ match_name(sdb_store_matcher_t *m, sdb_host_t *host) } /* match_name */ static int -match_attr(sdb_store_matcher_t *m, sdb_host_t *host) +match_attr(sdb_store_matcher_t *m, sdb_host_t *host, + sdb_store_matcher_t *filter) { sdb_attribute_t *attr; assert(m->type == MATCHER_ATTR); assert(ATTR_M(m)->name); - attr = attr_get(host, ATTR_M(m)->name); + attr = attr_get(host, ATTR_M(m)->name, filter); if (attr) { char buf[sdb_data_strlen(&attr->value) + 1]; if (sdb_data_format(&attr->value, buf, sizeof(buf), SDB_UNQUOTED) <= 0) @@ -218,58 +233,65 @@ match_attr(sdb_store_matcher_t *m, sdb_host_t *host) } /* match_attr */ static int -match_lt(sdb_store_matcher_t *m, sdb_host_t *host) +match_lt(sdb_store_matcher_t *m, sdb_host_t *host, + sdb_store_matcher_t *filter) { int status; assert(m->type == MATCHER_LT); - status = COND_M(m)->cond->cmp(host, COND_M(m)->cond); + status = COND_M(m)->cond->cmp(host, COND_M(m)->cond, filter); return (status != INT_MAX) && (status < 0); } /* match_lt */ static int -match_le(sdb_store_matcher_t *m, sdb_host_t *host) +match_le(sdb_store_matcher_t *m, sdb_host_t *host, + sdb_store_matcher_t *filter) { int status; assert(m->type == MATCHER_LE); - status = COND_M(m)->cond->cmp(host, COND_M(m)->cond); + status = COND_M(m)->cond->cmp(host, COND_M(m)->cond, filter); return (status != INT_MAX) && (status <= 0); } /* match_le */ static int -match_eq(sdb_store_matcher_t *m, sdb_host_t *host) +match_eq(sdb_store_matcher_t *m, sdb_host_t *host, + sdb_store_matcher_t *filter) { int status; assert(m->type == MATCHER_EQ); - status = COND_M(m)->cond->cmp(host, COND_M(m)->cond); + status = COND_M(m)->cond->cmp(host, COND_M(m)->cond, filter); return (status != INT_MAX) && (! status); } /* match_eq */ static int -match_ge(sdb_store_matcher_t *m, sdb_host_t *host) +match_ge(sdb_store_matcher_t *m, sdb_host_t *host, + sdb_store_matcher_t *filter) { int status; assert(m->type == MATCHER_GE); - status = COND_M(m)->cond->cmp(host, COND_M(m)->cond); + status = COND_M(m)->cond->cmp(host, COND_M(m)->cond, filter); return (status != INT_MAX) && (status >= 0); } /* match_ge */ static int -match_gt(sdb_store_matcher_t *m, sdb_host_t *host) +match_gt(sdb_store_matcher_t *m, sdb_host_t *host, + sdb_store_matcher_t *filter) { int status; assert(m->type == MATCHER_GT); - status = COND_M(m)->cond->cmp(host, COND_M(m)->cond); + status = COND_M(m)->cond->cmp(host, COND_M(m)->cond, filter); return (status != INT_MAX) && (status > 0); } /* match_gt */ static int -match_isnull(sdb_store_matcher_t *m, sdb_host_t *host) +match_isnull(sdb_store_matcher_t *m, sdb_host_t *host, + sdb_store_matcher_t *filter) { assert(m->type == MATCHER_ISNULL); - return attr_get(host, ISNULL_M(m)->attr_name) == NULL; + return attr_get(host, ISNULL_M(m)->attr_name, filter) == NULL; } /* match_isnull */ -typedef int (*matcher_cb)(sdb_store_matcher_t *, sdb_host_t *); +typedef int (*matcher_cb)(sdb_store_matcher_t *, sdb_host_t *, + sdb_store_matcher_t *); /* this array needs to be indexable by the matcher types; * -> update the enum in store-private.h when updating this */ @@ -885,11 +907,15 @@ sdb_store_inv_matcher(sdb_store_matcher_t *m) } /* sdb_store_inv_matcher */ int -sdb_store_matcher_matches(sdb_store_matcher_t *m, sdb_store_obj_t *obj) +sdb_store_matcher_matches(sdb_store_matcher_t *m, sdb_store_obj_t *obj, + sdb_store_matcher_t *filter) { if (obj->type != SDB_HOST) return 0; + if (filter && (! sdb_store_matcher_matches(filter, obj, NULL))) + return 0; + /* "NULL" always matches */ if ((! m) || (! obj)) return 1; @@ -897,7 +923,7 @@ sdb_store_matcher_matches(sdb_store_matcher_t *m, sdb_store_obj_t *obj) if ((m->type < 0) || ((size_t)m->type >= SDB_STATIC_ARRAY_LEN(matchers))) return 0; - return matchers[m->type](m, HOST(obj)); + return matchers[m->type](m, HOST(obj), filter); } /* sdb_store_matcher_matches */ char * @@ -913,10 +939,10 @@ sdb_store_matcher_tostring(sdb_store_matcher_t *m, char *buf, size_t buflen) } /* sdb_store_matcher_tostring */ int -sdb_store_scan(sdb_store_matcher_t *m, sdb_store_lookup_cb cb, - void *user_data) +sdb_store_scan(sdb_store_matcher_t *m, sdb_store_matcher_t *filter, + sdb_store_lookup_cb cb, void *user_data) { - scan_iter_data_t data = { m, cb, user_data }; + scan_iter_data_t data = { m, filter, cb, user_data }; if (! cb) return -1; diff --git a/src/frontend/query.c b/src/frontend/query.c index 2a6aecf..675b396 100644 --- a/src/frontend/query.c +++ b/src/frontend/query.c @@ -166,7 +166,7 @@ sdb_fe_lookup(sdb_conn_t *conn, sdb_store_matcher_t *m) sdb_strbuf_append(buf, "["); - if (sdb_store_scan(m, lookup_tojson, buf)) { + if (sdb_store_scan(m, /* filter */ NULL, lookup_tojson, buf)) { sdb_log(SDB_LOG_ERR, "frontend: Failed to lookup hosts"); sdb_strbuf_sprintf(conn->errbuf, "Failed to lookup hosts"); sdb_strbuf_destroy(buf); diff --git a/src/include/core/store.h b/src/include/core/store.h index 574b01e..578fb51 100644 --- a/src/include/core/store.h +++ b/src/include/core/store.h @@ -316,14 +316,22 @@ sdb_store_inv_matcher(sdb_store_matcher_t *m); /* * sdb_store_matcher_matches: - * Check whether the specified matcher matches the specified store object. + * Check whether the specified matcher matches the specified store object. If + * specified, the filter will be used to preselect objects for further + * evaluation. It is applied to any object that's used during the evaluation + * of the matcher. Only those objects matching the filter will be considered. + * + * Note that the filter is applied to all object types (hosts, service, + * attribute). Thus, any object-specific matchers are mostly unsuited for this + * purpose and, if used, may result in unexpected behavior. * * Returns: * - 1 if the object matches * - 0 else */ int -sdb_store_matcher_matches(sdb_store_matcher_t *m, sdb_store_obj_t *obj); +sdb_store_matcher_matches(sdb_store_matcher_t *m, sdb_store_obj_t *obj, + sdb_store_matcher_t *filter); /* * sdb_store_matcher_tostring: @@ -344,15 +352,17 @@ typedef int (*sdb_store_lookup_cb)(sdb_store_obj_t *obj, void *user_data); * sdb_store_scan: * Look up objects in the store. The specified callback function is called for * each object in the store matching 'm'. The function performs a full scan of - * all hosts stored in the database. + * all hosts stored in the database. If specified, the filter will be used to + * preselect objects for further evaluation. See the description of + * 'sdb_store_matcher_matches' for details. * * Returns: * - 0 on success * - a negative value else */ int -sdb_store_scan(sdb_store_matcher_t *m, sdb_store_lookup_cb cb, - void *user_data); +sdb_store_scan(sdb_store_matcher_t *m, sdb_store_matcher_t *filter, + sdb_store_lookup_cb cb, void *user_data); /* * Flags for serialization functions. diff --git a/t/unit/core/store_lookup_test.c b/t/unit/core/store_lookup_test.c index 27c03d7..c6eeb6b 100644 --- a/t/unit/core/store_lookup_test.c +++ b/t/unit/core/store_lookup_test.c @@ -140,12 +140,14 @@ START_TEST(test_store_match_name) m = sdb_store_name_matcher(golden_data[i].type, golden_data[i].name, golden_data[i].re); fail_unless(m != NULL, - "sdb_store_service_matcher(%d, %s, %d) = NULL; expected: ", - golden_data[i].type, golden_data[i].name, golden_data[i].re); + "sdb_store_service_matcher(%d, %s, %d) = NULL; " + "expected: ", golden_data[i].type, + golden_data[i].name, golden_data[i].re); - status = sdb_store_matcher_matches(m, obj); + status = sdb_store_matcher_matches(m, obj, /* filter */ NULL); fail_unless(status == golden_data[i].expected, - "sdb_store_matcher_matches(%s, ) = %d; expected: %d", + "sdb_store_matcher_matches(%s, , NULL) = %d; " + "expected: %d", sdb_store_matcher_tostring(m, buf, sizeof(buf)), status, golden_data[i].expected); @@ -155,9 +157,10 @@ START_TEST(test_store_match_name) sdb_object_deref(SDB_OBJ(m)); /* now match the inverted set of objects */ - status = sdb_store_matcher_matches(n, obj); + status = sdb_store_matcher_matches(n, obj, /* filter */ NULL); fail_unless(status == !golden_data[i].expected, - "sdb_store_matcher_matches(%s, ) = %d; expected: %d", + "sdb_store_matcher_matches(%s, , NULL) = %d; " + "expected: %d", sdb_store_matcher_tostring(n, buf, sizeof(buf)), status, !golden_data[i].expected); @@ -218,9 +221,10 @@ START_TEST(test_store_match_attr) fail_unless(m != NULL, "sdb_store_attr_matcher() = NULL; expected: "); - status = sdb_store_matcher_matches(m, obj); + status = sdb_store_matcher_matches(m, obj, /* filter */ NULL); fail_unless(status == golden_data[i].expected, - "sdb_store_matcher_matches({%s, ) = %d; expected: %d", + "sdb_store_matcher_matches({%s, , NULL) = %d; " + "expected: %d", sdb_store_matcher_tostring(m, buf, sizeof(buf)), status, golden_data[i].expected); @@ -230,9 +234,10 @@ START_TEST(test_store_match_attr) sdb_object_deref(SDB_OBJ(m)); /* now match the inverted set of objects */ - status = sdb_store_matcher_matches(n, obj); + status = sdb_store_matcher_matches(n, obj, /* filter */ NULL); fail_unless(status == !golden_data[i].expected, - "sdb_store_matcher_matches({%s, ) = %d; expected: %d", + "sdb_store_matcher_matches({%s, , NULL) = %d; " + "expected: %d", sdb_store_matcher_tostring(n, buf, sizeof(buf)), status, !golden_data[i].expected); @@ -313,9 +318,10 @@ START_TEST(test_store_cond) fail_unless(m != NULL, "sdb_store__matcher() = NULL; expected: "); - status = sdb_store_matcher_matches(m, obj); + status = sdb_store_matcher_matches(m, obj, /* filter */ NULL); fail_unless(status == *tests[j].expected, - "sdb_store_matcher_matches(%s) = %d; expected: %d", + "sdb_store_matcher_matches(%s, , NULL) = %d; " + "expected: %d", sdb_store_matcher_tostring(m, m_str, sizeof(m_str)), status, *tests[j].expected); @@ -357,10 +363,10 @@ START_TEST(test_store_match_op) obj = sdb_store_get_host("a"); - status = sdb_store_matcher_matches(always, obj); + status = sdb_store_matcher_matches(always, obj, /* filter */ NULL); fail_unless(status == 1, "INTERNAL ERROR: 'always' did not match host"); - status = sdb_store_matcher_matches(never, obj); + status = sdb_store_matcher_matches(never, obj, /* filter */ NULL); fail_unless(status == 0, "INTERNAL ERROR: 'never' matches host"); @@ -381,9 +387,9 @@ START_TEST(test_store_match_op) #define TO_NAME(v) (((v) == always) ? "always" \ : ((v) == never) ? "never" : "") - status = sdb_store_matcher_matches(m, obj); + status = sdb_store_matcher_matches(m, obj, /* filter */ NULL); fail_unless(status == golden_data[i].expected, - "%s(%s, %s) = %d; expected: %d", golden_data[i].op, + "%s(%s, %s, NULL) = %d; expected: %d", golden_data[i].op, TO_NAME(golden_data[i].left), TO_NAME(golden_data[i].right), status, golden_data[i].expected); @@ -527,55 +533,61 @@ START_TEST(test_scan) #define PTR_RE "0x[0-9a-f]+" struct { const char *query; + const char *filter; int expected; const char *tostring_re; } golden_data[] = { - { "host = 'a'", 1, + { "host = 'a'", NULL, 1, + "OBJ\\[host\\]\\{ NAME\\{ 'a', \\(nil\\) \\} \\}" }, + { "host = 'a'", "host = 'b'", 0, "OBJ\\[host\\]\\{ NAME\\{ 'a', \\(nil\\) \\} \\}" }, - { "host =~ 'a|b'", 2, + { "host = 'a'", + "attribute.x IS NULL", 1, + "OBJ\\[host\\]\\{ NAME\\{ 'a', \\(nil\\) \\} \\}" }, + { "host =~ 'a|b'", NULL, 2, "OBJ\\[host\\]\\{ NAME\\{ NULL, "PTR_RE" \\} \\}" }, - { "host =~ 'host'", 0, + { "host =~ 'host'", NULL, 0, "OBJ\\[host\\]\\{ NAME\\{ NULL, "PTR_RE" \\} \\}" }, - { "host =~ '.'", 3, + { "host =~ '.'", NULL, 3, "OBJ\\[host\\]\\{ NAME\\{ NULL, "PTR_RE" \\} \\}" }, - { "service = 's1'", 2, + { "service = 's1'", NULL, 2, "OBJ\\[service\\]\\{ NAME\\{ 's1', \\(nil\\) } \\}" }, - { "service =~ 's'", 2, + { "service =~ 's'", NULL, 2, "OBJ\\[service\\]\\{ NAME\\{ NULL, "PTR_RE" } \\}" }, - { "service !~ 's'", 1, + { "service !~ 's'", NULL, 1, "\\(NOT, OBJ\\[service\\]\\{ NAME\\{ NULL, "PTR_RE" } \\}\\)" }, - { "attribute = 'k1'", 1, + { "attribute = 'k1'", NULL, 1, "OBJ\\[attribute\\]\\{ NAME\\{ 'k1', \\(nil\\) \\} " }, - { "attribute = 'x'", 0, + { "attribute = 'x'", NULL, 0, "OBJ\\[attribute\\]\\{ NAME\\{ 'x', \\(nil\\) \\}" }, - { "attribute.k1 = 'v1'", 1, + { "attribute.k1 = 'v1'", NULL, 1, "ATTR\\[k1\\]\\{ VALUE\\{ 'v1', \\(nil\\) \\} \\}" }, - { "attribute.k1 IS NULL", 2, + { "attribute.k1 IS NULL", NULL, 2, "\\(IS NULL, ATTR\\[k1\\]\\)" }, - { "attribute.x1 IS NULL", 3, + { "attribute.x1 IS NULL", NULL, 3, "\\(IS NULL, ATTR\\[x1\\]\\)" }, - { "attribute.k1 IS NOT NULL", 1, + { "attribute.k1 IS NOT NULL", NULL, 1, "\\(NOT, \\(IS NULL, ATTR\\[k1\\]\\)\\)" }, - { "attribute.x1 IS NOT NULL", 0, + { "attribute.x1 IS NOT NULL", NULL, 0, "\\(NOT, \\(IS NULL, ATTR\\[x1\\]\\)\\)" }, - { "attribute.k2 < 123", 0, + { "attribute.k2 < 123", NULL, 0, "ATTR\\[k2\\]\\{ < 123 \\}" }, - { "attribute.k2 <= 123", 1, + { "attribute.k2 <= 123", NULL, 1, "ATTR\\[k2\\]\\{ <= 123 \\}" }, - { "attribute.k2 >= 123", 1, + { "attribute.k2 >= 123", NULL, 1, "ATTR\\[k2\\]\\{ >= 123 \\}" }, - { "attribute.k2 > 123", 0, + { "attribute.k2 > 123", NULL, 0, "ATTR\\[k2\\]\\{ > 123 \\}" }, - { "attribute.k2 = 123", 1, + { "attribute.k2 = 123", NULL, 1, "ATTR\\[k2\\]\\{ = 123 \\}" }, - { "attribute.k2 != 123", 2, + { "attribute.k2 != 123", NULL, 2, "\\(NOT, ATTR\\[k2\\]\\{ = 123 \\}\\)" }, - { "attribute.k1 != 'v1'", 2, + { "attribute.k1 != 'v1'", NULL, 2, "\\(NOT, ATTR\\[k1\\]\\{ VALUE\\{ 'v1', \\(nil\\) \\} \\}\\)" }, - { "attribute.k1 != 'v2'", 3, + { "attribute.k1 != 'v2'", NULL, 3, "\\(NOT, ATTR\\[k1\\]\\{ VALUE\\{ 'v2', \\(nil\\) \\} \\}\\)" }, { "attribute != 'x' " - "AND attribute.y !~ 'x'", 3, + "AND attribute.y !~ 'x'", NULL, 3, "\\(AND, " "\\(NOT, OBJ\\[attribute\\]\\{ NAME\\{ 'x', \\(nil\\) \\} \\}\\), " "\\(NOT, ATTR\\[y\\]\\{ VALUE\\{ NULL, "PTR_RE" \\} \\}\\)\\)" }, @@ -585,20 +597,29 @@ START_TEST(test_scan) size_t i; n = 0; - check = sdb_store_scan(NULL, scan_cb, &n); + check = sdb_store_scan(/* matcher */ NULL, /* filter */ NULL, + scan_cb, &n); fail_unless(check == 0, "sdb_store_scan() = %d; expected: 0", check); fail_unless(n == 3, "sdb_store_scan called callback %d times; expected: 3", (int)n); for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) { - sdb_store_matcher_t *m; + sdb_store_matcher_t *m, *filter = NULL; char buf[4096]; m = sdb_fe_parse_matcher(golden_data[i].query, -1); fail_unless(m != NULL, "sdb_fe_parse_matcher(%s, -1) = NULL; expected: ", golden_data[i].query); + + if (golden_data[i].filter) { + filter = sdb_fe_parse_matcher(golden_data[i].filter, -1); + fail_unless(filter != NULL, + "sdb_fe_parse_matcher(%s, -1) = NULL; " + "expected: ", golden_data[i].filter); + } + fail_unless(sdb_regmatches(golden_data[i].tostring_re, sdb_store_matcher_tostring(m, buf, sizeof(buf))) == 0, "sdb_fe_parse_matcher(%s, -1) = %s; expected: %s", @@ -607,10 +628,12 @@ START_TEST(test_scan) golden_data[i].tostring_re); n = 0; - sdb_store_scan(m, scan_cb, &n); + sdb_store_scan(m, filter, scan_cb, &n); fail_unless(n == golden_data[i].expected, - "sdb_store_scan(matcher{%s}) found %d hosts; expected: %d", - golden_data[i].query, n, golden_data[i].expected); + "sdb_store_scan(matcher{%s}, filter{NULL}) found %d hosts; " + "expected: %d", golden_data[i].query, n, + golden_data[i].expected); + sdb_object_deref(SDB_OBJ(filter)); sdb_object_deref(SDB_OBJ(m)); } }