From 740f1a8839faaf3033ea23f4cf2eea0dc0209d13 Mon Sep 17 00:00:00 2001 From: Sebastian Harl Date: Sun, 17 May 2015 21:58:03 +0200 Subject: [PATCH] store, frontend: Add sdb_store_query_execute use it instead of sdb_fe_exec. This new function executes a generic QUERY matcher as returned by sdb_store_query_prepare and writes the reply to a string buffer. --- src/Makefile.am | 1 + src/core/store_exec.c | 381 ++++++++++++++++++++++++++++++ src/core/store_query.c | 2 +- src/frontend/query.c | 123 ++++------ src/frontend/store.c | 4 + src/include/core/store.h | 13 + src/include/frontend/connection.h | 18 +- t/unit/frontend/query_test.c | 9 +- 8 files changed, 455 insertions(+), 96 deletions(-) create mode 100644 src/core/store_exec.c diff --git a/src/Makefile.am b/src/Makefile.am index 6b560df..52bd42b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -78,6 +78,7 @@ libsysdb_la_SOURCES = \ core/plugin.c include/core/plugin.h \ core/store.c include/core/store.h \ core/store-private.h \ + core/store_exec.c \ core/store_expr.c \ core/store_json.c \ core/store_lookup.c \ diff --git a/src/core/store_exec.c b/src/core/store_exec.c new file mode 100644 index 0000000..949f9ff --- /dev/null +++ b/src/core/store_exec.c @@ -0,0 +1,381 @@ +/* + * SysDB - src/core/store_exec.c + * Copyright (C) 2014-2015 Sebastian 'tokkee' Harl + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "core/object.h" +#include "core/store-private.h" +#include "frontend/connection.h" +#include "parser/ast.h" +#include "utils/error.h" + +#include + +#include +#include +#include +#include + +/* + * private helper functions + */ + +static int +list_tojson(sdb_store_obj_t *obj, + sdb_store_matcher_t __attribute__((unused)) *filter, + void *user_data) +{ + sdb_store_json_formatter_t *f = user_data; + return sdb_store_json_emit(f, obj); +} /* list_tojson */ + +static int +lookup_tojson(sdb_store_obj_t *obj, sdb_store_matcher_t *filter, + void *user_data) +{ + sdb_store_json_formatter_t *f = user_data; + return sdb_store_json_emit_full(f, obj, filter); +} /* lookup_tojson */ + +static size_t +sstrlen(const char *s) +{ + return s ? strlen(s) : 0; +} /* sstrlen */ + +/* + * query implementations + */ + +static int +exec_fetch(sdb_strbuf_t *buf, sdb_strbuf_t *errbuf, int type, + const char *hostname, const char *name, sdb_store_matcher_t *filter) +{ + uint32_t res_type = htonl(SDB_CONNECTION_FETCH); + + sdb_store_obj_t *host; + sdb_store_obj_t *obj; + + sdb_store_json_formatter_t *f; + + if ((! name) || ((type == SDB_HOST) && hostname) + || ((type != SDB_HOST) && (! hostname))) { + /* This is a programming error, not something the client did wrong */ + sdb_strbuf_sprintf(errbuf, "INTERNAL ERROR: invalid " + "arguments to FETCH(%s, %s, %s)", + SDB_STORE_TYPE_TO_NAME(type), hostname, name); + return -1; + } + if (type == SDB_HOST) + hostname = name; + + host = sdb_store_get_host(hostname); + if ((! host) + || (filter && (! sdb_store_matcher_matches(filter, host, NULL)))) { + sdb_strbuf_sprintf(errbuf, "Failed to fetch %s %s: " + "host %s not found", SDB_STORE_TYPE_TO_NAME(type), + name, hostname); + sdb_object_deref(SDB_OBJ(host)); + return -1; + } + if (type == SDB_HOST) { + obj = host; + } + else { + obj = sdb_store_get_child(host, type, name); + if ((! obj) + || (filter && (! sdb_store_matcher_matches(filter, obj, NULL)))) { + sdb_strbuf_sprintf(errbuf, "Failed to fetch %s %s.%s: " + "%s not found", SDB_STORE_TYPE_TO_NAME(type), + hostname, name, name); + if (obj) + sdb_object_deref(SDB_OBJ(obj)); + sdb_object_deref(SDB_OBJ(host)); + return -1; + } + sdb_object_deref(SDB_OBJ(host)); + } + host = NULL; + + f = sdb_store_json_formatter(buf, type, /* flags = */ 0); + if (! f) { + char err[1024]; + sdb_log(SDB_LOG_ERR, "frontend: Failed to create " + "JSON formatter to handle FETCH command: %s", + sdb_strerror(errno, err, sizeof(err))); + + sdb_strbuf_sprintf(errbuf, "Out of memory"); + sdb_object_deref(SDB_OBJ(obj)); + return -1; + } + + sdb_strbuf_memcpy(buf, &res_type, sizeof(uint32_t)); + if (sdb_store_json_emit_full(f, obj, filter)) { + sdb_log(SDB_LOG_ERR, "frontend: Failed to serialize " + "%s %s.%s to JSON", SDB_STORE_TYPE_TO_NAME(type), + hostname, name); + sdb_strbuf_sprintf(errbuf, "Out of memory"); + free(f); + sdb_object_deref(SDB_OBJ(obj)); + return -1; + } + + sdb_object_deref(SDB_OBJ(obj)); + sdb_store_json_finish(f); + free(f); + + return SDB_CONNECTION_DATA; +} /* exec_fetch */ + +static int +exec_list(sdb_strbuf_t *buf, sdb_strbuf_t *errbuf, int type, + sdb_store_matcher_t *filter) +{ + uint32_t res_type = htonl(SDB_CONNECTION_LIST); + sdb_store_json_formatter_t *f; + + f = sdb_store_json_formatter(buf, type, SDB_WANT_ARRAY); + if (! f) { + char err[1024]; + sdb_log(SDB_LOG_ERR, "frontend: Failed to create " + "JSON formatter to handle LIST command: %s", + sdb_strerror(errno, err, sizeof(err))); + + sdb_strbuf_sprintf(errbuf, "Out of memory"); + return -1; + } + + sdb_strbuf_memcpy(buf, &res_type, sizeof(uint32_t)); + if (sdb_store_scan(type, /* m = */ NULL, filter, list_tojson, f)) { + sdb_log(SDB_LOG_ERR, "frontend: Failed to serialize " + "store to JSON"); + sdb_strbuf_sprintf(errbuf, "Out of memory"); + free(f); + return -1; + } + + sdb_store_json_finish(f); + free(f); + + return SDB_CONNECTION_DATA; +} /* exec_list */ + +static int +exec_lookup(sdb_strbuf_t *buf, sdb_strbuf_t *errbuf, int type, + sdb_store_matcher_t *m, sdb_store_matcher_t *filter) +{ + uint32_t res_type = htonl(SDB_CONNECTION_LOOKUP); + sdb_store_json_formatter_t *f; + + f = sdb_store_json_formatter(buf, type, SDB_WANT_ARRAY); + if (! f) { + char err[1024]; + sdb_log(SDB_LOG_ERR, "frontend: Failed to create " + "JSON formatter to handle LOOKUP command: %s", + sdb_strerror(errno, err, sizeof(err))); + + sdb_strbuf_sprintf(errbuf, "Out of memory"); + return -1; + } + + sdb_strbuf_memcpy(buf, &res_type, sizeof(uint32_t)); + + if (sdb_store_scan(type, m, filter, lookup_tojson, f)) { + sdb_log(SDB_LOG_ERR, "frontend: Failed to lookup %ss", + SDB_STORE_TYPE_TO_NAME(type)); + sdb_strbuf_sprintf(errbuf, "Failed to lookup %ss", + SDB_STORE_TYPE_TO_NAME(type)); + free(f); + return -1; + } + + sdb_store_json_finish(f); + free(f); + + return SDB_CONNECTION_DATA; +} /* exec_lookup */ + +static int +exec_store(sdb_strbuf_t *buf, sdb_strbuf_t *errbuf, sdb_ast_store_t *st) +{ + char name[sstrlen(st->hostname) + sstrlen(st->parent) + sstrlen(st->name) + 3]; + sdb_metric_store_t metric_store; + int type = st->obj_type, status = -1; + + switch (st->obj_type) { + case SDB_HOST: + strncpy(name, st->name, sizeof(name)); + status = sdb_store_host(st->name, st->last_update); + break; + + case SDB_SERVICE: + snprintf(name, sizeof(name), "%s.%s", st->hostname, st->name); + status = sdb_store_service(st->hostname, st->name, st->last_update); + break; + + case SDB_METRIC: + snprintf(name, sizeof(name), "%s.%s", st->hostname, st->name); + metric_store.type = st->store_type; + metric_store.id = st->store_id; + status = sdb_store_metric(st->hostname, st->name, + &metric_store, st->last_update); + break; + + case SDB_ATTRIBUTE: + type |= st->parent_type; + + if (st->parent) + snprintf(name, sizeof(name), "%s.%s.%s", + st->hostname, st->parent, st->name); + else + snprintf(name, sizeof(name), "%s.%s", st->hostname, st->name); + + switch (st->parent_type) { + case 0: + type |= SDB_HOST; + status = sdb_store_attribute(st->hostname, + st->name, &st->value, st->last_update); + break; + + case SDB_SERVICE: + status = sdb_store_service_attr(st->hostname, st->parent, + st->name, &st->value, st->last_update); + break; + + case SDB_METRIC: + status = sdb_store_metric_attr(st->hostname, st->parent, + st->name, &st->value, st->last_update); + break; + + default: + sdb_log(SDB_LOG_ERR, "store: Invalid parent type in STORE: %s", + SDB_STORE_TYPE_TO_NAME(st->parent_type)); + return -1; + } + break; + + default: + sdb_log(SDB_LOG_ERR, "store: Invalid object type in STORE: %s", + SDB_STORE_TYPE_TO_NAME(st->obj_type)); + return -1; + } + + if (status < 0) { + sdb_strbuf_sprintf(errbuf, "STORE: Failed to store %s object", + SDB_STORE_TYPE_TO_NAME(type)); + return -1; + } + + if (! status) { + sdb_strbuf_sprintf(buf, "Successfully stored %s %s", + SDB_STORE_TYPE_TO_NAME(type), name); + } + else { + char type_str[32]; + strncpy(type_str, SDB_STORE_TYPE_TO_NAME(type), sizeof(type_str)); + type_str[0] = (char)toupper((int)type_str[0]); + sdb_strbuf_sprintf(buf, "%s %s already up to date", type_str, name); + } + + return SDB_CONNECTION_OK; +} /* exec_store */ + +static int +exec_timeseries(sdb_strbuf_t *buf, sdb_strbuf_t *errbuf, + const char *hostname, const char *metric, + sdb_timeseries_opts_t *opts) +{ + uint32_t res_type = htonl(SDB_CONNECTION_TIMESERIES); + + sdb_strbuf_memcpy(buf, &res_type, sizeof(uint32_t)); + if (sdb_store_fetch_timeseries(hostname, metric, opts, buf)) { + sdb_log(SDB_LOG_ERR, "frontend: Failed to fetch time-series"); + sdb_strbuf_sprintf(errbuf, "Failed to fetch time-series"); + return -1; + } + + return SDB_CONNECTION_DATA; +} /* exec_timeseries */ + +/* + * public API + */ + +int +sdb_store_query_execute(sdb_store_matcher_t *m, + sdb_strbuf_t *buf, sdb_strbuf_t *errbuf) +{ + sdb_timeseries_opts_t ts_opts; + sdb_ast_node_t *ast; + + if ((! m) || (m->type != MATCHER_QUERY)) { + int t = m ? m-> type : -1; + sdb_log(SDB_LOG_ERR, "store: Invalid query of type %s", MATCHER_SYM(t)); + return -1; + } + if (! QUERY_M(m)->ast) { + sdb_log(SDB_LOG_ERR, "store: Invalid empty query"); + return -1; + } + + ast = QUERY_M(m)->ast; + switch (ast->type) { + case SDB_AST_TYPE_FETCH: + return exec_fetch(buf, errbuf, SDB_AST_FETCH(ast)->obj_type, + SDB_AST_FETCH(ast)->hostname, SDB_AST_FETCH(ast)->name, + QUERY_M(m)->filter); + + case SDB_AST_TYPE_LIST: + return exec_list(buf, errbuf, SDB_AST_LIST(ast)->obj_type, + QUERY_M(m)->filter); + + case SDB_AST_TYPE_LOOKUP: + return exec_lookup(buf, errbuf, SDB_AST_LOOKUP(ast)->obj_type, + QUERY_M(m)->matcher, QUERY_M(m)->filter); + + case SDB_AST_TYPE_STORE: + if (ast->type != SDB_AST_TYPE_STORE) { + sdb_log(SDB_LOG_ERR, "store: Invalid AST node for STORE command: %s", + SDB_AST_TYPE_TO_STRING(ast)); + return -1; + } + return exec_store(buf, errbuf, SDB_AST_STORE(ast)); + + case SDB_AST_TYPE_TIMESERIES: + ts_opts.start = SDB_AST_TIMESERIES(ast)->start; + ts_opts.end = SDB_AST_TIMESERIES(ast)->end; + return exec_timeseries(buf, errbuf, SDB_AST_TIMESERIES(ast)->hostname, + SDB_AST_TIMESERIES(ast)->metric, &ts_opts); + + default: + sdb_log(SDB_LOG_ERR, "store: Invalid query of type %s", + SDB_AST_TYPE_TO_STRING(ast)); + return -1; + } + + return 0; +} /* sdb_store_query_execute */ + +/* vim: set tw=78 sw=4 ts=4 noexpandtab : */ diff --git a/src/core/store_query.c b/src/core/store_query.c index 90d45bf..88b076a 100644 --- a/src/core/store_query.c +++ b/src/core/store_query.c @@ -1,5 +1,5 @@ /* - * SysDB - src/core/store_lookup.c + * SysDB - src/core/store_query.c * Copyright (C) 2014-2015 Sebastian 'tokkee' Harl * All rights reserved. * diff --git a/src/frontend/query.c b/src/frontend/query.c index 2c80be7..149750b 100644 --- a/src/frontend/query.c +++ b/src/frontend/query.c @@ -29,7 +29,8 @@ #include "core/store.h" #include "frontend/connection-private.h" -#include "frontend/parser.h" +#include "parser/ast.h" +#include "parser/parser.h" #include "utils/error.h" #include "utils/proto.h" #include "utils/strbuf.h" @@ -66,13 +67,16 @@ int sdb_fe_query(sdb_conn_t *conn) { sdb_llist_t *parsetree; - sdb_conn_node_t *node = NULL; + sdb_ast_node_t *ast = NULL; + + sdb_store_matcher_t *q; + sdb_strbuf_t *buf = NULL; int status = 0; if ((! conn) || (conn->cmd != SDB_CONNECTION_QUERY)) return -1; - parsetree = sdb_fe_parse(sdb_strbuf_string(conn->buf), + parsetree = sdb_parser_parse(sdb_strbuf_string(conn->buf), (int)conn->cmd_len, conn->errbuf); if (! parsetree) { char query[conn->cmd_len + 1]; @@ -89,7 +93,7 @@ sdb_fe_query(sdb_conn_t *conn) sdb_connection_send(conn, SDB_CONNECTION_DATA, 0, NULL); break; case 1: - node = SDB_CONN_NODE(sdb_llist_get(parsetree, 0)); + ast = SDB_AST_NODE(sdb_llist_get(parsetree, 0)); break; default: @@ -102,17 +106,47 @@ sdb_fe_query(sdb_conn_t *conn) sdb_llist_len(parsetree) - 1, sdb_llist_len(parsetree) == 2 ? "" : "s", query); - node = SDB_CONN_NODE(sdb_llist_get(parsetree, 0)); + ast = SDB_AST_NODE(sdb_llist_get(parsetree, 0)); } } - if (node) { - status = sdb_fe_exec(conn, node); - sdb_object_deref(SDB_OBJ(node)); + q = sdb_store_query_prepare(ast); + if (! q) { + /* this shouldn't happen */ + sdb_strbuf_sprintf(conn->errbuf, "failed to compile AST"); + sdb_log(SDB_LOG_ERR, "frontend: failed to compile AST"); + status = -1; + } else { + buf = sdb_strbuf_create(1024); + if (! buf) { + sdb_strbuf_sprintf(conn->errbuf, "Out of memory"); + sdb_object_deref(SDB_OBJ(q)); + return -1; + } + status = sdb_store_query_execute(q, buf, conn->errbuf); + if (status < 0) { + char query[conn->cmd_len + 1]; + strncpy(query, sdb_strbuf_string(conn->buf), conn->cmd_len); + query[sizeof(query) - 1] = '\0'; + sdb_log(SDB_LOG_ERR, "frontend: failed to execute query '%s'", query); + } } - + sdb_object_deref(SDB_OBJ(ast)); sdb_llist_destroy(parsetree); - return status; + + if (status < 0) { + sdb_object_deref(SDB_OBJ(q)); + sdb_strbuf_destroy(buf); + return status; + } + + assert(buf); + sdb_connection_send(conn, status, + (uint32_t)sdb_strbuf_len(buf), sdb_strbuf_string(buf)); + + sdb_strbuf_destroy(buf); + sdb_object_deref(SDB_OBJ(q)); + return 0; } /* sdb_fe_query */ int @@ -225,72 +259,9 @@ sdb_fe_lookup(sdb_conn_t *conn) return status; } /* sdb_fe_lookup */ -int -sdb_fe_exec(sdb_conn_t *conn, sdb_conn_node_t *node) -{ - sdb_store_matcher_t *m = NULL, *filter = NULL; - - if (! node) - return -1; - - switch (node->cmd) { - case SDB_CONNECTION_FETCH: - if (CONN_FETCH(node)->filter) - filter = CONN_FETCH(node)->filter->matcher; - return sdb_fe_exec_fetch(conn, CONN_FETCH(node)->type, - CONN_FETCH(node)->host, CONN_FETCH(node)->name, filter); - case SDB_CONNECTION_LIST: - if (CONN_LIST(node)->filter) - filter = CONN_LIST(node)->filter->matcher; - return sdb_fe_exec_list(conn, CONN_LIST(node)->type, filter); - case SDB_CONNECTION_LOOKUP: - if (CONN_LOOKUP(node)->matcher) - m = CONN_LOOKUP(node)->matcher->matcher; - if (CONN_LOOKUP(node)->filter) - filter = CONN_LOOKUP(node)->filter->matcher; - return sdb_fe_exec_lookup(conn, - CONN_LOOKUP(node)->type, m, filter); - case SDB_CONNECTION_STORE_HOST: - { - conn_store_host_t *n = CONN_STORE_HOST(node); - sdb_proto_host_t host = { n->last_update, n->name }; - return sdb_fe_store_host(conn, &host); - } - case SDB_CONNECTION_STORE_SERVICE: - { - conn_store_svc_t *n = CONN_STORE_SVC(node); - sdb_proto_service_t svc = { n->last_update, n->hostname, n->name }; - return sdb_fe_store_service(conn, &svc); - } - case SDB_CONNECTION_STORE_METRIC: - { - conn_store_metric_t *n = CONN_STORE_METRIC(node); - sdb_proto_metric_t metric = { - n->last_update, n->hostname, n->name, - n->store_type, n->store_id - }; - return sdb_fe_store_metric(conn, &metric); - } - case SDB_CONNECTION_STORE_ATTRIBUTE: - { - conn_store_attr_t *n = CONN_STORE_ATTR(node); - sdb_proto_attribute_t attr = { - n->last_update, n->parent_type, n->hostname, n->parent, - n->key, n->value - }; - return sdb_fe_store_attribute(conn, &attr); - } - case SDB_CONNECTION_TIMESERIES: - return sdb_fe_exec_timeseries(conn, - CONN_TS(node)->hostname, CONN_TS(node)->metric, - &CONN_TS(node)->opts); - - default: - sdb_log(SDB_LOG_ERR, "frontend: Unknown command %i", node->cmd); - return -1; - } - return -1; -} /* sdb_fe_exec */ +/* + * TODO: let the functions above build an AST; then drop sdb_fe_exec_*. + */ int sdb_fe_exec_fetch(sdb_conn_t *conn, int type, diff --git a/src/frontend/store.c b/src/frontend/store.c index 6cad8cd..3d1a847 100644 --- a/src/frontend/store.c +++ b/src/frontend/store.c @@ -73,6 +73,10 @@ store_reply(sdb_conn_t *conn, int type, const char *name, int status) * public API */ +/* + * TODO: move sdb_fe_store to frontend/query.c and let it build an AST + */ + int sdb_fe_store(sdb_conn_t *conn) { diff --git a/src/include/core/store.h b/src/include/core/store.h index 0abf0cb..179b5d0 100644 --- a/src/include/core/store.h +++ b/src/include/core/store.h @@ -371,6 +371,19 @@ sdb_store_get_attr(sdb_store_obj_t *obj, const char *name, sdb_data_t *res, sdb_store_matcher_t * sdb_store_query_prepare(sdb_ast_node_t *ast); +/* + * sdb_store_query_execute: + * Execute a previously prepared query. The query result will be written to + * 'buf' and any errors to 'errbuf'. + * + * Returns: + * - the result type (to be used by the server reply) + * - a negative value on error + */ +int +sdb_store_query_execute(sdb_store_matcher_t *m, + sdb_strbuf_t *buf, sdb_strbuf_t *errbuf); + /* * sdb_store_expr_create: * Creates an arithmetic expression implementing the specified operator on the diff --git a/src/include/frontend/connection.h b/src/include/frontend/connection.h index 0397e50..98c4136 100644 --- a/src/include/frontend/connection.h +++ b/src/include/frontend/connection.h @@ -149,10 +149,9 @@ sdb_connection_server_version(sdb_conn_t *conn); /* * sdb_fe_parse: * Parse the query text specified in 'query' of length 'len' and return a list - * of parse trees (for each command) to be executed by sdb_fe_exec. The list - * has to be freed by the caller. If 'len' is less than zero, parse the whole - * (nul-terminated) string. If specified, errbuf will be used to record parse - * errors. + * of parse trees (for each command). The list has to be freed by the caller. + * If 'len' is less than zero, parse the whole (nul-terminated) string. If + * specified, errbuf will be used to record parse errors. * * Returns: * - an sdb_llist_t object of sdb_conn_node_t on success @@ -161,17 +160,6 @@ sdb_connection_server_version(sdb_conn_t *conn); sdb_llist_t * sdb_fe_parse(const char *query, int len, sdb_strbuf_t *errbuf); -/* - * sdb_fe_exec: - * Execute the command identified by 'node' on the specified connection. - * - * Returns: - * - 0 on success - * - a negative value else - */ -int -sdb_fe_exec(sdb_conn_t *conn, sdb_conn_node_t *node); - /* * session handling */ diff --git a/t/unit/frontend/query_test.c b/t/unit/frontend/query_test.c index 7e15502..2160255 100644 --- a/t/unit/frontend/query_test.c +++ b/t/unit/frontend/query_test.c @@ -504,7 +504,7 @@ static struct { }, { "STORE service attribute 'h2'.'s1'.'aA' 'vA'", - 0, SDB_CONNECTION_OK, 43, 0, "Successfully stored service attribute s1.aA", + 0, SDB_CONNECTION_OK, 46, 0, "Successfully stored service attribute h2.s1.aA", }, { "STORE service attribute 'h2'.'x1'.'aA' 'vA'", @@ -520,7 +520,7 @@ static struct { }, { "STORE metric attribute 'h1'.'m1'.'aA' 'vA'", - 0, SDB_CONNECTION_OK, 42, 0, "Successfully stored metric attribute m1.aA", + 0, SDB_CONNECTION_OK, 45, 0, "Successfully stored metric attribute h1.m1.aA", }, { "STORE metric attribute 'h1'.'x1'.'aA' 'vA'", @@ -544,8 +544,9 @@ START_TEST(test_query) check = sdb_fe_query(conn); fail_unless(check == query_data[_i].expected, - "sdb_fe_query(%s) = %d; expected: %d", - query_data[_i].query, check, query_data[_i].expected); + "sdb_fe_query(%s) = %d; expected: %d (err: %s)", + query_data[_i].query, check, query_data[_i].expected, + sdb_strbuf_string(conn->errbuf)); data = sdb_strbuf_string(MOCK_CONN(conn)->write_buf); len = sdb_strbuf_len(MOCK_CONN(conn)->write_buf); -- 2.30.2