From ed47b70d7db4b9bf9a1e0b38324d822f1017d835 Mon Sep 17 00:00:00 2001 From: Dmitry Simonenko <pmwkaa@gmail.com> Date: Thu, 14 Jun 2012 18:09:31 +0400 Subject: [PATCH] connector-c-librpl: console client refactoring. --- CMakeLists.txt | 2 +- client/tarantool/tnt.c | 506 ------------------ client/{tarantool => tc}/CMakeLists.txt | 16 +- client/tc/tc.c | 129 +++++ client/tc/tc.h | 37 ++ .../{tarantool/tnt_admin.c => tc/tc_admin.c} | 42 +- .../{tarantool/tnt_admin.h => tc/tc_admin.h} | 22 +- client/tc/tc_cli.c | 176 ++++++ client/tc/tc_cli.h | 32 ++ client/tc/tc_opt.c | 121 +++++ client/tc/tc_opt.h | 54 ++ client/tc/tc_print.c | 75 +++ client/tc/tc_print.h | 32 ++ client/tc/tc_query.c | 157 ++++++ client/tc/tc_query.h | 41 ++ client/tc/tc_wal.c | 175 ++++++ client/tc/tc_wal.h | 33 ++ connector/c/include/tarantool/tnt_iter.h | 1 + connector/c/include/tarantool/tnt_rpl.h | 2 +- connector/c/tntrpl/tnt_rpl.c | 10 +- connector/c/tntsql/tnt_sql.c | 2 +- test/connector_c/rpl.c | 15 +- 22 files changed, 1110 insertions(+), 570 deletions(-) delete mode 100644 client/tarantool/tnt.c rename client/{tarantool => tc}/CMakeLists.txt (73%) create mode 100644 client/tc/tc.c create mode 100644 client/tc/tc.h rename client/{tarantool/tnt_admin.c => tc/tc_admin.c} (82%) rename client/{tarantool/tnt_admin.h => tc/tc_admin.h} (75%) create mode 100644 client/tc/tc_cli.c create mode 100644 client/tc/tc_cli.h create mode 100644 client/tc/tc_opt.c create mode 100644 client/tc/tc_opt.h create mode 100644 client/tc/tc_print.c create mode 100644 client/tc/tc_print.h create mode 100644 client/tc/tc_query.c create mode 100644 client/tc/tc_query.h create mode 100644 client/tc/tc_wal.c create mode 100644 client/tc/tc_wal.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 341a488035..c3f20af588 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -222,7 +222,7 @@ option(ENABLE_DOC "Enable building of documentation" OFF) option(ENABLE_CLIENT "Enable building of console client" OFF) if (ENABLE_CLIENT) - set (TARANTOOL_CLIENTS ${TARANTOOL_CLIENTS} "tarantool") + set (TARANTOOL_CLIENTS ${TARANTOOL_CLIENTS} "tc") endif() option(ENABLE_GCOV "Enable integration with gcov, a code coverage program" OFF) diff --git a/client/tarantool/tnt.c b/client/tarantool/tnt.c deleted file mode 100644 index 2fd57aa339..0000000000 --- a/client/tarantool/tnt.c +++ /dev/null @@ -1,506 +0,0 @@ - -/* - * Copyright (C) 2011 Mail.RU - * - * 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 AUTHOR 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 AUTHOR 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 <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <ctype.h> -#include <inttypes.h> -#include <limits.h> - -#include <signal.h> -#include <errno.h> - -#include <readline/readline.h> -#include <readline/history.h> - -#include <errcode.h> -#include <third_party/gopt/gopt.h> - -#include <connector/c/include/tarantool/tnt.h> -#include <connector/c/include/tarantool/tnt_sql.h> -#include <connector/c/include/tarantool/tnt_net.h> -#include <connector/c/include/tarantool/tnt_xlog.h> -#include <connector/c/include/tarantool/tnt_rpl.h> - -#include <client/tarantool/tnt_admin.h> - -#define DEFAULT_HOST "localhost" -#define DEFAULT_PORT 33013 -#define DEFAULT_PORT_ADMIN 33015 -#define HISTORY_FILE ".tarantool_history" - -static char *query_op_type(uint32_t type) { - switch (type) { - case TNT_OP_PING: return "Ping"; - case TNT_OP_INSERT: return "Insert"; - case TNT_OP_DELETE: return "Delete"; - case TNT_OP_UPDATE: return "Update"; - case TNT_OP_SELECT: return "Select"; - case TNT_OP_CALL: return "Call"; - } - return "Unknown"; -} - -static char *query_op(struct tnt_reply *r) { - return query_op_type(r->op); -} - -static void print_tuple(struct tnt_tuple *tu) { - struct tnt_iter ifl; - tnt_iter(&ifl, tu); - printf("["); - while (tnt_next(&ifl)) { - if (TNT_IFIELD_IDX(&ifl) != 0) - printf(", "); - char *data = TNT_IFIELD_DATA(&ifl); - uint32_t size = TNT_IFIELD_SIZE(&ifl); - if (!isprint(data[0]) && (size == 4 || size == 8)) { - if (size == 4) { - uint32_t i = *((uint32_t*)data); - printf("%"PRIu32, i); - } else { - uint64_t i = *((uint64_t*)data); - printf("%"PRIu64, i); - } - } else { - printf("'%-.*s'", size, data); - } - } - if (ifl.status == TNT_ITER_FAIL) - printf("<parsing error>"); - printf("]\n"); -} - -static void print_tuple_list(struct tnt_list *l) { - struct tnt_iter it; - tnt_iter_list(&it, l); - while (tnt_next(&it)) { - struct tnt_tuple *tu = TNT_ILIST_TUPLE(&it); - print_tuple(tu); - } -} - -static void -query_reply_show(struct tnt_reply *r) -{ - printf("%s OK, %d rows affected\n", query_op(r), r->count); - print_tuple_list(TNT_REPLY_LIST(r)); -} - -static int -query_reply(struct tnt_stream *t, int show_reply) -{ - int rc = -1; - struct tnt_iter i; - tnt_iter_reply(&i, t); - while (tnt_next(&i)) { - struct tnt_reply *r = TNT_IREPLY_PTR(&i); - if (tnt_error(t) != TNT_EOK) { - printf("%s ERROR, %s\n", query_op(r), - tnt_strerror(t)); - goto error; - } else if (r->code != 0) { - printf("%s ERROR, %s (%s)\n", query_op(r), - ((r->error) ? r->error : ""), tnt_errcode_str(r->code >> 8)); - goto error; - } - if (show_reply) - query_reply_show(r); - - } - rc = (i.status == TNT_ITER_FAIL) ? -1 : 0; -error: - tnt_iter_free(&i); - return rc; -} - -static int -query(struct tnt_stream *t, char *q) -{ - char *e = NULL; - int rc = tnt_query(t, q, strlen(q), &e); - if (rc == -1) { - if (e) { - printf("error: %s", e); - free(e); - } - return -1; - } - rc = tnt_flush(t); - if (rc < 0) { - printf("error: %s\n", tnt_strerror(t)); - return -1; - } - if (query_reply(t, 1) == -1) - return -1; - return 0; -} - -static int -query_admin(struct tnt_admin *a, char *q, int reply) -{ - if (tnt_admin_query(a, q) == -1) { - printf("error: failed to send admin query\n"); - return -1; - } - if (!reply) - return 0; - char *rep = NULL; - size_t reps = 0; - if (tnt_admin_reply(a, &rep, &reps) == -1) { - printf("error: failed to recv admin reply\n"); - return -1; - } - if (rep) { - printf("%s", rep); - free(rep); - } - return 0; -} - -static int -run_cmdline(struct tnt_stream *t, struct tnt_admin *a, int argc, char **argv) -{ - int i, rc = 0; - for (i = 1 ; i < argc ; i++) { - if (tnt_query_is(argv[i], strlen(argv[i]))) { - if (query(t, argv[i]) == -1) - rc = 1; - } else { - int reply = strcmp(argv[i], "exit") && strcmp(argv[i], "quit"); - if (query_admin(a, argv[i], reply) == -1) - rc = 1; - if (!reply) - break; - } - } - return rc; -} - -static int reconnect_do(struct tnt_stream *t, struct tnt_admin *a) { - if (tnt_connect(t) == -1) { - printf("reconnect: %s\n", tnt_strerror(t)); - return 0; - } - if (tnt_admin_reconnect(a) == -1) { - printf("reconnect: admin console connection failed\n"); - return 0; - } - printf("reconnected\n"); - return 1; -} - -static int -run_interactive(struct tnt_stream *t, struct tnt_admin *a, char *host) -{ - /* ignoring SIGPIPE */ - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sigemptyset(&sa.sa_mask); - sa.sa_handler = SIG_IGN; - if (sigaction(SIGPIPE, &sa, NULL) == -1) { - printf("signal initialization failed\n"); - return 1; - } - - char *home = getenv("HOME"); - char history[1024]; - snprintf(history, sizeof(history), "%s/%s", home, HISTORY_FILE); - read_history(history); - - char prompt[128]; - snprintf(prompt, sizeof(prompt), "%s> ", host); - - /* interactive mode */ - int reconnect = 0; - char *cmd; - while ((cmd = readline(prompt))) { - if (!cmd[0]) - goto next; - if (reconnect) { -reconnect: if (reconnect_do(t, a)) - reconnect = 0; - else - goto next; - } - if (tnt_query_is(cmd, strlen(cmd))) { - if (query(t, cmd) == -1) { - /* broken pipe or recv() == 0 */ - int e = tnt_errno(t) == EPIPE || tnt_errno(t) == 0; - if (tnt_error(t) == TNT_ESYSTEM && e) - reconnect = 1; - } - } else { - int reply = strcmp(cmd, "exit") && strcmp(cmd, "quit"); - if (query_admin(a, cmd, reply) == -1) - reconnect = 1; - if (!reply) { - free(cmd); - break; - } - } - add_history(cmd); - if (reconnect) - goto reconnect; -next: - free(cmd); - } - - write_history(history); - clear_history(); - return 0; -} - -static int -run_wal_cat(const char *file) -{ - struct tnt_stream s; - tnt_xlog(&s); - if (tnt_xlog_open(&s, (char*)file) == -1) { - tnt_stream_free(&s); - return -1; - } - struct tnt_iter i; - tnt_iter_request(&i, &s); - while (tnt_next(&i)) { - struct tnt_request *r = TNT_IREQUEST_PTR(&i); - struct tnt_stream_xlog *sx = TNT_SXLOG_CAST(&s); - printf("%s lsn: %"PRIu64", time: %f, len: %"PRIu32"\n", - query_op_type(r->h.type), - sx->hdr.lsn, - sx->hdr.tm, sx->hdr.len); - switch (r->h.type) { - case TNT_OP_INSERT: - print_tuple(&r->r.insert.t); - break; - case TNT_OP_DELETE: - print_tuple(&r->r.delete.t); - break; - case TNT_OP_UPDATE: - print_tuple(&r->r.update.t); - break; - case TNT_OP_CALL: - print_tuple(&r->r.call.t); - break; - } - } - int rc = 0; - if (i.status == TNT_ITER_FAIL) { - printf("parsing failed: %s\n", tnt_xlog_strerror(&s)); - rc = 1; - } - tnt_iter_free(&i); - tnt_stream_free(&s); - return rc; -} - -static int -run_wal_play(struct tnt_stream *t, const char *file) -{ - struct tnt_stream s; - tnt_xlog(&s); - if (tnt_xlog_open(&s, (char*)file) == -1) { - tnt_stream_free(&s); - return -1; - } - struct tnt_iter i; - tnt_iter_request(&i, &s); - while (tnt_next(&i)) { - struct tnt_request *r = TNT_IREQUEST_PTR(&i); - if (t->write_request(t, r) == -1) { - printf("failed to write request\n"); - goto error; - } - if (query_reply(t, 0) == -1) - goto error; - } - if (i.status == TNT_ITER_FAIL) { - printf("parsing failed: %s\n", tnt_xlog_strerror(&s)); - goto error; - } - return 0; -error: - tnt_iter_free(&i); - tnt_stream_free(&s); - return 1; -} - -static int -run_replica(char *host, int port, uint64_t lsn) -{ - struct tnt_stream s; - tnt_rpl(&s); - - struct tnt_stream *sn = tnt_rpl_net(&s); - tnt_set(sn, TNT_OPT_HOSTNAME, host); - tnt_set(sn, TNT_OPT_PORT, port); - tnt_set(sn, TNT_OPT_SEND_BUF, 0); - tnt_set(sn, TNT_OPT_RECV_BUF, 0); - if (tnt_rpl_open(&s, lsn) == -1) - return 1; - - struct tnt_iter i; - tnt_iter_request(&i, &s); - - while (tnt_next(&i)) { - struct tnt_request *r = TNT_IREQUEST_PTR(&i); - struct tnt_stream_rpl *sr = TNT_RPL_CAST(&s); - printf("%s lsn: %"PRIu64"\n", - query_op_type(r->h.type), - sr->hdr.lsn); - switch (r->h.type) { - case TNT_OP_INSERT: - print_tuple(&r->r.insert.t); - break; - case TNT_OP_DELETE: - print_tuple(&r->r.delete.t); - break; - case TNT_OP_UPDATE: - print_tuple(&r->r.update.t); - break; - case TNT_OP_CALL: - print_tuple(&r->r.call.t); - break; - } - } - if (i.status == TNT_ITER_FAIL) - printf("parsing failed\n"); - - tnt_iter_free(&i); - tnt_stream_free(&s); - return 0; -} - -int -main(int argc, char *argv[]) -{ - const void *opt_def = - gopt_start(gopt_option('a', GOPT_ARG, gopt_shorts('a'), - gopt_longs("host"), " <host>", "server address"), - gopt_option('p', GOPT_ARG, gopt_shorts('p'), - gopt_longs("port"), " <port>", "server port"), - gopt_option('m', GOPT_ARG, gopt_shorts('m'), - gopt_longs("port-admin"), " <port>", "server admin port"), - gopt_option('C', GOPT_ARG, gopt_shorts('C'), - gopt_longs("wal-cat"), " <file>", "print xlog file content"), - gopt_option('P', GOPT_ARG, gopt_shorts('P'), - gopt_longs("wal-play"), " <file>", "replay xlog file to the specified server"), - gopt_option('R', GOPT_ARG, gopt_shorts('R'), - gopt_longs("rpl"), " <lsn>", "act as replica for to the specified server"), - gopt_option('h', 0, gopt_shorts('h', '?'), gopt_longs("help"), - NULL, "display this help and exit")); - void *opt = gopt_sort(&argc, (const char**)argv, opt_def); - if (gopt(opt, 'h')) { - printf("usage: tarantool [options] [query]\n\n"); - printf("tarantool sql client.\n"); - gopt_help(opt_def); - gopt_free(opt); - return 0; - } - - /* wal-cat */ - const char *arg = NULL; - if (gopt_arg(opt, 'C', &arg)) - return run_wal_cat(arg); - - /* server host */ - gopt_arg(opt, 'a', &arg); - - char host[128]; - snprintf(host, sizeof(host), "%s", (arg) ? arg : DEFAULT_HOST); - - /* server port */ - int port = DEFAULT_PORT; - if (gopt_arg(opt, 'p', &arg)) - port = atoi(arg); - - /* replica mode */ - if (gopt_arg(opt, 'R', &arg)) { - uint64_t lsn = strtoll(arg, NULL, 10); - if (lsn == LLONG_MIN || lsn == LLONG_MAX) { - printf("bad lsn number\n"); - return 1; - } - return run_replica(host, port, lsn); - } - - /* server admin port */ - int admin_port = DEFAULT_PORT_ADMIN; - if (gopt_arg(opt, 'm', &arg)) - admin_port = atoi(arg); - - /* wal-player mode */ - const char *wal_player_file = NULL; - gopt_arg(opt, 'P', &wal_player_file); - gopt_free(opt); - - /* creating and initializing tarantool network stream */ - struct tnt_stream *t = tnt_net(NULL); - if (t == NULL) - return 1; - tnt_set(t, TNT_OPT_HOSTNAME, host); - tnt_set(t, TNT_OPT_PORT, port); - tnt_set(t, TNT_OPT_SEND_BUF, 0); - tnt_set(t, TNT_OPT_RECV_BUF, 0); - if (tnt_init(t) == -1) { - printf("error: %s\n", tnt_strerror(t)); - tnt_stream_free(t); - return 1; - } - - /* connecting to server */ - if (tnt_connect(t) == -1) { - printf("error: %s\n", tnt_strerror(t)); - tnt_stream_free(t); - return 1; - } - - /* wal-player mode */ - if (wal_player_file) { - int rc = run_wal_play(t, wal_player_file); - tnt_stream_free(t); - return rc; - } - - /* creating tarantool admin handler */ - struct tnt_admin a; - if (tnt_admin_init(&a, host, admin_port) == -1) { - printf("error: admin console initialization failed\n"); - tnt_stream_free(t); - return 1; - } - - /* main */ - int rc = 0; - if (argc >= 2) - rc = run_cmdline(t, &a, argc, argv); - else - rc = run_interactive(t, &a, host); - tnt_admin_free(&a); - tnt_stream_free(t); - return rc; -} diff --git a/client/tarantool/CMakeLists.txt b/client/tc/CMakeLists.txt similarity index 73% rename from client/tarantool/CMakeLists.txt rename to client/tc/CMakeLists.txt index 9379d4f0f9..f866ac8288 100644 --- a/client/tarantool/CMakeLists.txt +++ b/client/tc/CMakeLists.txt @@ -1,4 +1,3 @@ - project(tnt_cli) include(FindCurses) @@ -19,18 +18,15 @@ set (cli_deps ${READLINE_LIB} ${READLINE_HISTORY_LIB}) if (CURSES_FOUND) set(cli_deps ${cli_deps} ${CURSES_LIBRARIES}) else() - message(FATAL_ERROR "curses library not found.") -endif() - -find_library(TERMCAP_LIBRARY NAMES "termcap") -if (TERMCAP_LIBRARY) - set(cli_deps ${cli_deps} ${TERMCAP_LIBRARY}) -else() - message(FATAL_ERROR "termcap library not found.") + # readline could be compiled with termcap + find_library(TERMCAP_LIB NAMES "termcap") + if (TERMCAP_LIB) + set(cli_deps ${cli_deps} ${TERMCAP_LIB}) + endif() endif() set (cli "tarantool") -set (cli_sources tnt.c tnt_admin.c) +set (cli_sources tc.c tc_opt.c tc_admin.c tc_query.c tc_print.c tc_cli.c tc_wal.c) set (cli_libs tntrpl tntnet tntsql tnt gopt ${cli_deps}) add_executable(${cli} ${cli_sources} ${CMAKE_SOURCE_DIR}/src/errcode.c) diff --git a/client/tc/tc.c b/client/tc/tc.c new file mode 100644 index 0000000000..ad491d5bed --- /dev/null +++ b/client/tc/tc.c @@ -0,0 +1,129 @@ + +/* + * Copyright (C) 2012 Mail.RU + * + * 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 AUTHOR 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 AUTHOR 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 <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <stdint.h> + +#include <connector/c/include/tarantool/tnt.h> +#include <connector/c/include/tarantool/tnt_net.h> +#include <connector/c/include/tarantool/tnt_sql.h> + +#include "client/tc/tc_opt.h" +#include "client/tc/tc_admin.h" +#include "client/tc/tc.h" +#include "client/tc/tc_cli.h" +#include "client/tc/tc_wal.h" + +struct tc tc; + +static void tc_init(void) { + memset(&tc, 0, sizeof(tc)); +} + +static void tc_free(void) { + if (tc.net) { + tnt_stream_free(tc.net); + } + tc_admin_close(&tc.admin); +} + +void tc_error(char *fmt, ...) { + char msg[256]; + va_list args; + tc_free(); + /* - - - - */ + va_start(args, fmt); + vsnprintf(msg, sizeof(msg), fmt, args); + va_end(args); + printf("error: %s\n", msg); + exit(1); +} + +static void tc_connect(void) +{ + /* allocating stream */ + tc.net = tnt_net(NULL); + if (tc.net == NULL) + tc_error("stream allocation error"); + /* initializing network stream */ + tnt_set(tc.net, TNT_OPT_HOSTNAME, tc.opt.host); + tnt_set(tc.net, TNT_OPT_PORT, tc.opt.port); + tnt_set(tc.net, TNT_OPT_SEND_BUF, 0); + tnt_set(tc.net, TNT_OPT_RECV_BUF, 0); + if (tnt_init(tc.net) == -1) + tc_error("%s", tnt_strerror(tc.net)); + /* connecting to server */ + if (tnt_connect(tc.net) == -1) + tc_error("%s", tnt_strerror(tc.net)); +} + +static void tc_connect_admin(void) +{ + if (tc_admin_connect(&tc.admin, + tc.opt.host, + tc.opt.port_admin) == -1) + tc_error("admin console connection failed"); +} + +int main(int argc, char *argv[]) +{ + tc_init(); + + int rc; + enum tc_opt_mode mode = tc_opt_init(&tc.opt, argc, argv); + switch (mode) { + case TC_OPT_USAGE: + tc_opt_usage(); + break; + case TC_OPT_RPL: + tc_connect(); + rc = tc_wal_remote(); + break; + case TC_OPT_WAL_CAT: + rc = tc_wal_cat(); + break; + case TC_OPT_WAL_PLAY: + tc_connect(); + rc = tc_wal_play(); + break; + case TC_OPT_CMD: + tc_connect(); + tc_connect_admin(); + rc = tc_cli_cmdv(); + break; + case TC_OPT_INTERACTIVE: + tc_connect(); + tc_connect_admin(); + rc = tc_cli(); + break; + } + + tc_free(); + return rc; +} diff --git a/client/tc/tc.h b/client/tc/tc.h new file mode 100644 index 0000000000..49166f1389 --- /dev/null +++ b/client/tc/tc.h @@ -0,0 +1,37 @@ +#ifndef TC_H_INCLUDED +#define TC_H_INCLUDED + +/* + * Copyright (C) 2012 Mail.RU + * + * 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 AUTHOR 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 AUTHOR 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. + */ + +struct tc { + struct tc_opt opt; + struct tc_admin admin; + struct tnt_stream *net; +}; + +void tc_error(char *fmt, ...); + +#endif /* TC_H_INCLUDED */ diff --git a/client/tarantool/tnt_admin.c b/client/tc/tc_admin.c similarity index 82% rename from client/tarantool/tnt_admin.c rename to client/tc/tc_admin.c index b5d7b16c29..ad5fdb739a 100644 --- a/client/tarantool/tnt_admin.c +++ b/client/tc/tc_admin.c @@ -1,6 +1,6 @@ /* - * Copyright (C) 2011 Mail.RU + * Copyright (C) 2012 Mail.RU * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -41,11 +41,12 @@ #include <errno.h> #include <limits.h> -#include <client/tarantool/tnt_admin.h> +#include "client/tc/tc_admin.h" -static int -tnt_admin_connect(struct tnt_admin *a) +int tc_admin_connect(struct tc_admin *a, const char *host, int port) { + a->host = host; + a->port = port; a->fd = socket(AF_INET, SOCK_STREAM, 0); if (a->fd < 0) return -1; @@ -69,32 +70,21 @@ tnt_admin_connect(struct tnt_admin *a) return -1; } -int -tnt_admin_init(struct tnt_admin *a, char *host, int port) +int tc_admin_reconnect(struct tc_admin *a) { - a->host = host; - a->port = port; - return tnt_admin_connect(a); + tc_admin_close(a); + return tc_admin_connect(a, a->host, a->port); } -int -tnt_admin_reconnect(struct tnt_admin *a) -{ - tnt_admin_free(a); - return tnt_admin_connect(a); -} - -void -tnt_admin_free(struct tnt_admin *a) +void tc_admin_close(struct tc_admin *a) { if (a->fd > 0) close(a->fd); - a->fd = -1; + a->fd = 0; } static int -tnt_admin_send(struct tnt_admin *a, char *buf, size_t size) -{ +tc_admin_send(struct tc_admin *a, char *buf, size_t size) { ssize_t rc, off = 0; do { rc = send(a->fd, buf + off, size - off, 0); @@ -105,18 +95,16 @@ tnt_admin_send(struct tnt_admin *a, char *buf, size_t size) return 0; } -int -tnt_admin_query(struct tnt_admin *a, char *q) +int tc_admin_query(struct tc_admin *a, char *q) { - if (tnt_admin_send(a, q, strlen(q)) == -1) + if (tc_admin_send(a, q, strlen(q)) == -1) return -1; - if (tnt_admin_send(a, "\n", 1) == -1) + if (tc_admin_send(a, "\n", 1) == -1) return -1; return 0; } -int -tnt_admin_reply(struct tnt_admin *a, char **r, size_t *size) +int tc_admin_reply(struct tc_admin *a, char **r, size_t *size) { char *buf = NULL; size_t off = 0; diff --git a/client/tarantool/tnt_admin.h b/client/tc/tc_admin.h similarity index 75% rename from client/tarantool/tnt_admin.h rename to client/tc/tc_admin.h index 43e34cb20f..e981611977 100644 --- a/client/tarantool/tnt_admin.h +++ b/client/tc/tc_admin.h @@ -1,8 +1,8 @@ -#ifndef TNT_ADMIN_H_INCLUDED -#define TNT_ADMIN_H_INCLUDED +#ifndef TC_ADMIN_H_INCLUDED +#define TC_ADMIN_H_INCLUDED /* - * Copyright (C) 2011 Mail.RU + * Copyright (C) 2012 Mail.RU * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -26,18 +26,18 @@ * SUCH DAMAGE. */ -struct tnt_admin { - char *host; +struct tc_admin { + const char *host; int port; int fd; }; -int tnt_admin_init(struct tnt_admin *a, char *host, int port); -void tnt_admin_free(struct tnt_admin *a); +int tc_admin_connect(struct tc_admin *a, const char *host, int port); +int tc_admin_reconnect(struct tc_admin *a); -int tnt_admin_reconnect(struct tnt_admin *a); +void tc_admin_close(struct tc_admin *a); -int tnt_admin_query(struct tnt_admin *a, char *q); -int tnt_admin_reply(struct tnt_admin *a, char **r, size_t *size); +int tc_admin_query(struct tc_admin *a, char *q); +int tc_admin_reply(struct tc_admin *a, char **r, size_t *size); -#endif /* TNT_ADMIN_H_INCLUDED */ +#endif /* TC_ADMIN_H_INCLUDED */ diff --git a/client/tc/tc_cli.c b/client/tc/tc_cli.c new file mode 100644 index 0000000000..7b4a88e86d --- /dev/null +++ b/client/tc/tc_cli.c @@ -0,0 +1,176 @@ + +/* + * Copyright (C) 2012 Mail.RU + * + * 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 AUTHOR 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 AUTHOR 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 <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <stdint.h> + +#include <signal.h> +#include <errno.h> + +#include <readline/readline.h> +#include <readline/history.h> + +#include <connector/c/include/tarantool/tnt.h> +#include <connector/c/include/tarantool/tnt_net.h> +#include <connector/c/include/tarantool/tnt_sql.h> + +#include "client/tc/tc_opt.h" +#include "client/tc/tc_admin.h" +#include "client/tc/tc.h" +#include "client/tc/tc_query.h" +#include "client/tc/tc_cli.h" + +#define TC_DEFAULT_HISTORY_FILE ".tarantool_history" + +extern struct tc tc; + +static inline int tc_cli_error(char *e) { + if (e) { + printf("%s\n", e); + free(e); + } + return 1; +} + +static int tc_cli_reconnect(void) { + if (tnt_connect(tc.net) == -1) { + printf("reconnect: %s\n", tnt_strerror(tc.net)); + return 1; + } + if (tc_admin_reconnect(&tc.admin) == -1) { + printf("reconnect: admin console connection failed\n"); + return 1; + } + printf("reconnected\n"); + return 0; +} + +enum tc_cli_cmd_ret { + TC_CLI_OK, + TC_CLI_ERROR, + TC_CLI_EXIT +}; + +static enum tc_cli_cmd_ret tc_cli_cmd(char *cmd, size_t size) +{ + int reconnect = 0; + do { + if (reconnect) { + reconnect = tc_cli_reconnect(); + if (reconnect) + return TC_CLI_ERROR; + } + char *e; + if (tnt_query_is(cmd, size)) { + if (tc_query(cmd, &e) == 0) { + if (tc_query_foreach(tc_query_printer, NULL, &e) == -1) + reconnect = tc_cli_error(e); + } else { + reconnect = tc_cli_error(e); + } + /* reconnect only for network errors */ + if (reconnect && tnt_error(tc.net) != TNT_ESYSTEM) + reconnect = 0; + } else { + int reply = strcmp(cmd, "exit") && + strcmp(cmd, "quit"); + tc_query_admin_t cb = tc_query_admin_printer; + if (!reply) + cb = NULL; + if (tc_query_admin(cmd, cb, &e) == -1) + reconnect = tc_cli_error(e); + if (!reply) + return TC_CLI_EXIT; + } + } while (reconnect); + + return TC_CLI_OK; +} + +int tc_cli_cmdv(void) +{ + int i, rc = 0; + for (i = 0 ; i < tc.opt.cmdc ; i++) { + enum tc_cli_cmd_ret ret = + tc_cli_cmd(tc.opt.cmdv[i], strlen(tc.opt.cmdv[i])); + if (ret == TC_CLI_EXIT) + break; + if (ret == TC_CLI_ERROR) { + rc = 1; + break; + } + } + return rc; +} + +static void tc_cli_init(void) { + /* ignoring SIGPIPE for reconnection handling */ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_IGN; + if (sigaction(SIGPIPE, &sa, NULL) == -1) + tc_error("signal initialization failed\n"); +} + +int tc_cli(void) +{ + /* initializing cli */ + tc_cli_init(); + + /* loading history file */ + char *home = getenv("HOME"); + char history[1024]; + snprintf(history, sizeof(history), "%s/%s", home, + TC_DEFAULT_HISTORY_FILE); + read_history(history); + + /* setting prompt */ + char prompt[128]; + snprintf(prompt, sizeof(prompt), "%s> ", tc.opt.host); + + /* interactive mode */ + char *cmd; + while ((cmd = readline(prompt))) { + if (!cmd[0]) + goto next; + enum tc_cli_cmd_ret ret = + tc_cli_cmd(cmd, strlen(cmd)); + if (ret == TC_CLI_EXIT) + break; + add_history(cmd); +next: + free(cmd); + } + + /* updating history file */ + write_history(history); + clear_history(); + return 0; +} diff --git a/client/tc/tc_cli.h b/client/tc/tc_cli.h new file mode 100644 index 0000000000..03eba7f7a7 --- /dev/null +++ b/client/tc/tc_cli.h @@ -0,0 +1,32 @@ +#ifndef TC_CLI_H_INCLUDED +#define TC_CLI_H_INCLUDED + +/* + * Copyright (C) 2012 Mail.RU + * + * 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 AUTHOR 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 AUTHOR 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. + */ + +int tc_cli_cmdv(void); +int tc_cli(void); + +#endif /* TC_CLI_H_INCLUDED */ diff --git a/client/tc/tc_opt.c b/client/tc/tc_opt.c new file mode 100644 index 0000000000..9e9e1903e1 --- /dev/null +++ b/client/tc/tc_opt.c @@ -0,0 +1,121 @@ + +/* + * Copyright (C) 2012 Mail.RU + * + * 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 AUTHOR 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 AUTHOR 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 <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdint.h> + +#include <third_party/gopt/gopt.h> + +#include "client/tc/tc_opt.h" + +#define TC_DEFAULT_HOST "localhost" +#define TC_DEFAULT_PORT 33013 +#define TC_DEFAULT_PORT_ADMIN 33015 + +/* supported cli options */ +static const void *tc_options_def = gopt_start( + gopt_option('a', GOPT_ARG, gopt_shorts('a'), + gopt_longs("host"), " <host>", "server address"), + gopt_option('p', GOPT_ARG, gopt_shorts('p'), + gopt_longs("port"), " <port>", "server port"), + gopt_option('m', GOPT_ARG, gopt_shorts('m'), + gopt_longs("port-admin"), " <port>", "server admin port"), + gopt_option('C', GOPT_ARG, gopt_shorts('C'), + gopt_longs("wal-cat"), " <file>", "print xlog file content"), + gopt_option('P', GOPT_ARG, gopt_shorts('P'), + gopt_longs("wal-play"), " <file>", "replay xlog file to the specified server"), + gopt_option('R', GOPT_ARG, gopt_shorts('R'), + gopt_longs("rpl"), " <lsn>", "act as replica for the specified server"), + gopt_option('h', 0, gopt_shorts('h', '?'), gopt_longs("help"), + NULL, "display this help and exit") +); + +void tc_opt_usage(void) +{ + printf("usage: tarantool [options] [query]\n\n"); + printf("tarantool cli.\n"); + gopt_help(tc_options_def); + exit(0); +} + +enum tc_opt_mode tc_opt_init(struct tc_opt *opt, int argc, char **argv) +{ + /* usage */ + void *tc_options = gopt_sort(&argc, (const char**)argv, tc_options_def); + if (gopt(tc_options, 'h')) { + opt->mode = TC_OPT_USAGE; + goto done; + } + + /* server host */ + gopt_arg(tc_options, 'a', &opt->host); + if (opt->host == NULL) + opt->host = TC_DEFAULT_HOST; + + /* server port */ + const char *arg = NULL; + opt->port = TC_DEFAULT_PORT; + if (gopt_arg(tc_options, 'p', &arg)) + opt->port = atoi(arg); + + /* server admin port */ + opt->port_admin = TC_DEFAULT_PORT_ADMIN; + if (gopt_arg(tc_options, 'm', &arg)) + opt->port_admin = atoi(arg); + + /* replica mode */ + if (gopt_arg(tc_options, 'R', &arg)) { + opt->mode = TC_OPT_RPL; + opt->lsn = strtoll(arg, NULL, 10); + goto done; + } + + /* wal-cat mode */ + if (gopt_arg(tc_options, 'C', &opt->xlog)) { + opt->mode = TC_OPT_WAL_CAT; + goto done; + } + + /* wal-play mode */ + if (gopt_arg(tc_options, 'P', &opt->xlog)) { + opt->mode = TC_OPT_WAL_PLAY; + goto done; + } + + /* default */ + if (argc >= 2) { + opt->cmdv = argv + 1; + opt->cmdc = argc - 1; + opt->mode = TC_OPT_CMD; + } else { + opt->mode = TC_OPT_INTERACTIVE; + } +done: + gopt_free(tc_options); + return opt->mode; +} diff --git a/client/tc/tc_opt.h b/client/tc/tc_opt.h new file mode 100644 index 0000000000..2e7ff605c7 --- /dev/null +++ b/client/tc/tc_opt.h @@ -0,0 +1,54 @@ +#ifndef TC_OPT_H_INCLUDED +#define TC_OPT_H_INCLUDED + +/* + * Copyright (C) 2012 Mail.RU + * + * 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 AUTHOR 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 AUTHOR 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. + */ + +enum tc_opt_mode { + TC_OPT_USAGE, + TC_OPT_RPL, + TC_OPT_WAL_CAT, + TC_OPT_WAL_PLAY, + TC_OPT_CMD, + TC_OPT_INTERACTIVE +}; + +struct tc_opt { + enum tc_opt_mode mode; + const char *host; + int port; + int port_admin; + uint64_t lsn; + const char *xlog; + char **cmdv; + int cmdc; +}; + +void tc_opt_usage(void); + +enum tc_opt_mode +tc_opt_init(struct tc_opt *opt, int argc, char **argv); + +#endif /* TC_OPT_H_INCLUDED */ diff --git a/client/tc/tc_print.c b/client/tc/tc_print.c new file mode 100644 index 0000000000..7457e0eb23 --- /dev/null +++ b/client/tc/tc_print.c @@ -0,0 +1,75 @@ + +/* + * Copyright (C) 2012 Mail.RU + * + * 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 AUTHOR 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 AUTHOR 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 <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <inttypes.h> +#include <string.h> +#include <ctype.h> + +#include <connector/c/include/tarantool/tnt.h> + +#include "client/tc/tc_print.h" + +void tc_print_tuple(struct tnt_tuple *tu) +{ + struct tnt_iter ifl; + tnt_iter(&ifl, tu); + printf("["); + while (tnt_next(&ifl)) { + if (TNT_IFIELD_IDX(&ifl) != 0) + printf(", "); + char *data = TNT_IFIELD_DATA(&ifl); + uint32_t size = TNT_IFIELD_SIZE(&ifl); + if (!isprint(data[0]) && (size == 4 || size == 8)) { + if (size == 4) { + uint32_t i = *((uint32_t*)data); + printf("%"PRIu32, i); + } else { + uint64_t i = *((uint64_t*)data); + printf("%"PRIu64, i); + } + } else { + printf("'%-.*s'", size, data); + } + } + if (ifl.status == TNT_ITER_FAIL) + printf("<parsing error>"); + printf("]\n"); + tnt_iter_free(&ifl); +} + +void tc_print_list(struct tnt_list *l) +{ + struct tnt_iter it; + tnt_iter_list(&it, l); + while (tnt_next(&it)) { + struct tnt_tuple *tu = TNT_ILIST_TUPLE(&it); + tc_print_tuple(tu); + } + tnt_iter_free(&it); +} diff --git a/client/tc/tc_print.h b/client/tc/tc_print.h new file mode 100644 index 0000000000..cefe8a1e69 --- /dev/null +++ b/client/tc/tc_print.h @@ -0,0 +1,32 @@ +#ifndef TC_PRINT_H_INCLUDED +#define TC_PRINT_H_INCLUDED + +/* + * Copyright (C) 2012 Mail.RU + * + * 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 AUTHOR 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 AUTHOR 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. + */ + +void tc_print_tuple(struct tnt_tuple *tu); +void tc_print_list(struct tnt_list *l); + +#endif /* TC_PRINT_H_INCLUDED */ diff --git a/client/tc/tc_query.c b/client/tc/tc_query.c new file mode 100644 index 0000000000..709ca41eb1 --- /dev/null +++ b/client/tc/tc_query.c @@ -0,0 +1,157 @@ + +/* + * Copyright (C) 2012 Mail.RU + * + * 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 AUTHOR 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 AUTHOR 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 <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <stdint.h> + +#include <include/errcode.h> + +#include <connector/c/include/tarantool/tnt.h> +#include <connector/c/include/tarantool/tnt_net.h> +#include <connector/c/include/tarantool/tnt_sql.h> + +#include "client/tc/tc_opt.h" +#include "client/tc/tc_admin.h" +#include "client/tc/tc.h" +#include "client/tc/tc_print.h" +#include "client/tc/tc_query.h" + +extern struct tc tc; + +char *tc_query_type(uint32_t type) { + switch (type) { + case TNT_OP_PING: return "Ping"; + case TNT_OP_INSERT: return "Insert"; + case TNT_OP_DELETE: return "Delete"; + case TNT_OP_UPDATE: return "Update"; + case TNT_OP_SELECT: return "Select"; + case TNT_OP_CALL: return "Call"; + } + return "Unknown"; +} + +static char *tc_query_op(struct tnt_reply *r) { + return tc_query_type(r->op); +} + +int tc_query_printer(struct tnt_reply *r, void *ptr, char **e) { + (void)ptr; + (void)e; + printf("%s OK, %d rows affected\n", tc_query_op(r), + r->count); + tc_print_list(TNT_REPLY_LIST(r)); + return 0; +} + +static char *tc_query_error(char *fmt, ...) { + char msg[256]; + va_list args; + va_start(args, fmt); + vsnprintf(msg, sizeof(msg), fmt, args); + va_end(args); + char *ptr = strdup(msg); + if (ptr == NULL) + tc_error("memory allocation failed"); + return ptr; +} + +int tc_query_foreach(tc_query_t cb, void *cba, char **e) +{ + int rc = -1; + struct tnt_iter i; + tnt_iter_reply(&i, tc.net); + while (tnt_next(&i)) { + struct tnt_reply *r = TNT_IREPLY_PTR(&i); + if (tnt_error(tc.net) != TNT_EOK) { + *e = tc_query_error("%s ERROR, %s", + tc_query_op(r), + tnt_strerror(tc.net)); + goto error; + } else if (r->code != 0) { + *e = tc_query_error("%s ERROR, %s (%s)", + tc_query_op(r), ((r->error) ? r->error : ""), + tnt_errcode_str(r->code >> 8)); + goto error; + } + /* invoking callback if supplied */ + if (cb) { + if (cb(r, cba, e) == -1) + goto error; + } + } + rc = (i.status == TNT_ITER_FAIL) ? -1 : 0; +error: + tnt_iter_free(&i); + return rc; +} + +int tc_query(char *q, char **e) { + int rc = tnt_query(tc.net, q, strlen(q), e); + if (rc == -1) + return -1; + rc = tnt_flush(tc.net); + if (rc < 0) { + char *ee = tnt_strerror(tc.net); + if (ee) { + *e = tc_query_error("%s", ee); + } + return -1; + } + return 0; +} + +int tc_query_admin_printer(char *r, char **e) { + (void)e; + printf("%s", r); + return 0; +} + +int tc_query_admin(char *q, tc_query_admin_t cb, char **e) +{ + if (tc_admin_query(&tc.admin, q) == -1) { + *e = tc_query_error("failed to send admin query"); + return -1; + } + if (cb == NULL) + return 0; + char *reply = NULL; + size_t reply_size = 0; + if (tc_admin_reply(&tc.admin, &reply, &reply_size) == -1) { + *e = tc_query_error("failed to recv admin reply"); + return -1; + } + if (cb && reply) { + if (cb(reply, e) == -1) { + free(reply); + return -1; + } + } + free(reply); + return 0; +} diff --git a/client/tc/tc_query.h b/client/tc/tc_query.h new file mode 100644 index 0000000000..b672e1556b --- /dev/null +++ b/client/tc/tc_query.h @@ -0,0 +1,41 @@ +#ifndef TC_QUERY_H_INCLUDED +#define TC_QUERY_H_INCLUDED + +/* + * Copyright (C) 2012 Mail.RU + * + * 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 AUTHOR 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 AUTHOR 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. + */ + +typedef int (*tc_query_t)(struct tnt_reply *r, void *ptr, char **e); +typedef int (*tc_query_admin_t)(char *r, char **e); + +char *tc_query_type(uint32_t type); + +int tc_query_printer(struct tnt_reply *r, void *ptr, char **e); +int tc_query_foreach(tc_query_t cb, void *cba, char **e); +int tc_query(char *q, char **e); + +int tc_query_admin_printer(char *r, char **e); +int tc_query_admin(char *q, tc_query_admin_t cb, char **e); + +#endif /* TC_QUERY_H_INCLUDED */ diff --git a/client/tc/tc_wal.c b/client/tc/tc_wal.c new file mode 100644 index 0000000000..ef4e207a9a --- /dev/null +++ b/client/tc/tc_wal.c @@ -0,0 +1,175 @@ + +/* + * Copyright (C) 2012 Mail.RU + * + * 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 AUTHOR 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 AUTHOR 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 <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <inttypes.h> +#include <limits.h> +#include <stdint.h> + +#include <connector/c/include/tarantool/tnt.h> +#include <connector/c/include/tarantool/tnt_net.h> +#include <connector/c/include/tarantool/tnt_sql.h> +#include <connector/c/include/tarantool/tnt_xlog.h> +#include <connector/c/include/tarantool/tnt_rpl.h> + +#include "client/tc/tc_opt.h" +#include "client/tc/tc_admin.h" +#include "client/tc/tc.h" +#include "client/tc/tc_print.h" +#include "client/tc/tc_query.h" +#include "client/tc/tc_wal.h" + +extern struct tc tc; + +typedef int (*tc_wal_t)(struct tnt_iter *i); + +static int tc_wal_error(char *fmt, ...) { + char msg[256]; + va_list args; + va_start(args, fmt); + vsnprintf(msg, sizeof(msg), fmt, args); + va_end(args); + printf("error: %s\n", msg); + return -1; +} + +static int tc_wal_foreach(struct tnt_stream *s, tc_wal_t cb) { + struct tnt_iter i; + tnt_iter_request(&i, s); + while (tnt_next(&i)) { + if (cb(&i) == -1) { + tnt_iter_free(&i); + return -1; + } + } + int rc = 0; + if (i.status == TNT_ITER_FAIL) + rc = tc_wal_error("parsing failed"); + tnt_iter_free(&i); + return rc; +} + +static void tc_wal_print(struct tnt_xlog_header_v11 *hdr, + struct tnt_request *r) +{ + printf("%s lsn: %"PRIu64", time: %f, len: %"PRIu32"\n", + tc_query_type(r->h.type), + hdr->lsn, + hdr->tm, + hdr->len); + switch (r->h.type) { + case TNT_OP_INSERT: + tc_print_tuple(&r->r.insert.t); + break; + case TNT_OP_DELETE: + tc_print_tuple(&r->r.delete.t); + break; + case TNT_OP_UPDATE: + tc_print_tuple(&r->r.update.t); + break; + case TNT_OP_CALL: + tc_print_tuple(&r->r.call.t); + break; + } +} + +static int tc_wal_printer(struct tnt_iter *i) { + struct tnt_request *r = TNT_IREQUEST_PTR(i); + struct tnt_stream_xlog *s = + TNT_SXLOG_CAST(TNT_IREQUEST_STREAM(i)); + tc_wal_print(&s->hdr, r); + return 0; +} + +static int tc_wal_foreach_xlog(tc_wal_t cb) { + struct tnt_stream s; + tnt_xlog(&s); + if (tnt_xlog_open(&s, (char*)tc.opt.xlog) == -1) { + tnt_stream_free(&s); + return 1; + } + if (tc_wal_foreach(&s, cb) == -1) { + tnt_stream_free(&s); + return 1; + } + tnt_stream_free(&s); + return 0; +} + +int tc_wal_cat(void) +{ + return tc_wal_foreach_xlog(tc_wal_printer); +} + +static int tc_wal_resender(struct tnt_iter *i) { + struct tnt_request *r = TNT_IREQUEST_PTR(i); + if (tc.net->write_request(tc.net, r) == -1) + return tc_wal_error("failed to write request"); + char *e = NULL; + if (tc_query_foreach(NULL, NULL, &e) == -1) { + tc_wal_error("%s", e); + free(e); + return -1; + } + return 0; +} + +int tc_wal_play(void) +{ + return tc_wal_foreach_xlog(tc_wal_resender); +} + +static int tc_wal_printer_from_rpl(struct tnt_iter *i) { + struct tnt_request *r = TNT_IREQUEST_PTR(i); + struct tnt_stream_rpl *s = + TNT_RPL_CAST(TNT_IREQUEST_STREAM(i)); + tc_wal_print(&s->hdr, r); + return 0; +} + +int tc_wal_remote(void) +{ + if (tc.opt.lsn == LLONG_MAX || + tc.opt.lsn == LLONG_MIN) { + tc_wal_error("bad lsn number"); + return 1; + } + struct tnt_stream s; + tnt_rpl(&s); + tnt_rpl_attach(&s, tc.net); + int rc = 0; + if (tnt_rpl_open(&s, tc.opt.lsn) == -1) { + rc = 1; + goto done; + } + if (tc_wal_foreach(&s, tc_wal_printer_from_rpl) == -1) + rc = 1; +done: + tnt_stream_free(&s); + return rc; +} diff --git a/client/tc/tc_wal.h b/client/tc/tc_wal.h new file mode 100644 index 0000000000..c49cd59f62 --- /dev/null +++ b/client/tc/tc_wal.h @@ -0,0 +1,33 @@ +#ifndef TC_WAL_H_INCLUDED +#define TC_WAL_H_INCLUDED + +/* + * Copyright (C) 2012 Mail.RU + * + * 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 AUTHOR 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 AUTHOR 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. + */ + +int tc_wal_cat(void); +int tc_wal_play(void); +int tc_wal_remote(void); + +#endif /* TC_WAL_H_INCLUDED */ diff --git a/connector/c/include/tarantool/tnt_iter.h b/connector/c/include/tarantool/tnt_iter.h index 79f93d159b..950a87caab 100644 --- a/connector/c/include/tarantool/tnt_iter.h +++ b/connector/c/include/tarantool/tnt_iter.h @@ -80,6 +80,7 @@ struct tnt_iter_request { #define TNT_IREQUEST(I) (&(I)->data.request) #define TNT_IREQUEST_PTR(I) &TNT_IREQUEST(I)->r +#define TNT_IREQUEST_STREAM(I) TNT_IREQUEST(I)->s /* reply iterator */ diff --git a/connector/c/include/tarantool/tnt_rpl.h b/connector/c/include/tarantool/tnt_rpl.h index 1420df5cac..f5b1713850 100644 --- a/connector/c/include/tarantool/tnt_rpl.h +++ b/connector/c/include/tarantool/tnt_rpl.h @@ -35,7 +35,7 @@ struct tnt_stream_rpl { #define TNT_RPL_CAST(S) ((struct tnt_stream_rpl*)(S)->data) struct tnt_stream *tnt_rpl(struct tnt_stream *s); -struct tnt_stream *tnt_rpl_net(struct tnt_stream *s); +void tnt_rpl_attach(struct tnt_stream *s, struct tnt_stream *net); int tnt_rpl_open(struct tnt_stream *s, uint64_t lsn); void tnt_rpl_close(struct tnt_stream *s); diff --git a/connector/c/tntrpl/tnt_rpl.c b/connector/c/tntrpl/tnt_rpl.c index 4ae6b8ea96..4ed9a1ccef 100644 --- a/connector/c/tntrpl/tnt_rpl.c +++ b/connector/c/tntrpl/tnt_rpl.c @@ -44,7 +44,7 @@ static const uint32_t tnt_rpl_version = 11; static void tnt_rpl_free(struct tnt_stream *s) { struct tnt_stream_rpl *sr = TNT_RPL_CAST(s); if (sr->net) { - tnt_stream_free(sr->net); + /* network stream should not be free'd here */ sr->net = NULL; } tnt_mem_free(s->data); @@ -111,9 +111,7 @@ struct tnt_stream *tnt_rpl(struct tnt_stream *s) s->free = tnt_rpl_free; /* initializing internal data */ struct tnt_stream_rpl *sr = TNT_RPL_CAST(s); - sr->net = tnt_net(NULL); - if (sr->net == NULL) - goto error; + sr->net = NULL; return s; error: if (s->data) { @@ -181,6 +179,6 @@ void tnt_rpl_close(struct tnt_stream *s) { * * s - replication stream pointer */ -struct tnt_stream *tnt_rpl_net(struct tnt_stream *s) { - return TNT_RPL_CAST(s)->net; +void tnt_rpl_attach(struct tnt_stream *s, struct tnt_stream *net) { + TNT_RPL_CAST(s)->net = net; } diff --git a/connector/c/tntsql/tnt_sql.c b/connector/c/tntsql/tnt_sql.c index b8b80c279a..b6220052f0 100644 --- a/connector/c/tntsql/tnt_sql.c +++ b/connector/c/tntsql/tnt_sql.c @@ -55,7 +55,7 @@ tnt_sql_error(struct tnt_sql *sql, struct tnt_tk *last, char *fmt, ...) int line = (last) ? last->line : sql->l->line; int col = (last) ? last->col : sql->l->col; char msg[256]; - snprintf(msg, sizeof(msg), "%d:%d %s\n", line, col, msgu); + snprintf(msg, sizeof(msg), "%d:%d %s", line, col, msgu); if (sql->error == NULL) sql->error = tnt_mem_dup(msg); return false; diff --git a/test/connector_c/rpl.c b/test/connector_c/rpl.c index e9e6492fc8..d57112f317 100644 --- a/test/connector_c/rpl.c +++ b/test/connector_c/rpl.c @@ -55,15 +55,15 @@ main(int argc, char * argv[]) printf("usage %s: host port limit\n", argv[0]); return 1; } - struct tnt_stream s; tnt_rpl(&s); - - struct tnt_stream *sn = tnt_rpl_net(&s); - tnt_set(sn, TNT_OPT_HOSTNAME, argv[1]); - tnt_set(sn, TNT_OPT_PORT, atoi(argv[2])); - tnt_set(sn, TNT_OPT_SEND_BUF, 0); - tnt_set(sn, TNT_OPT_RECV_BUF, 0); + struct tnt_stream sn; + tnt_net(&sn); + tnt_set(&sn, TNT_OPT_HOSTNAME, argv[1]); + tnt_set(&sn, TNT_OPT_PORT, atoi(argv[2])); + tnt_set(&sn, TNT_OPT_SEND_BUF, 0); + tnt_set(&sn, TNT_OPT_RECV_BUF, 0); + tnt_rpl_attach(&s, &sn); if (tnt_rpl_open(&s, 2) == -1) return 1; @@ -83,5 +83,6 @@ main(int argc, char * argv[]) tnt_iter_free(&i); tnt_stream_free(&s); + tnt_stream_free(&sn); return 0; } -- GitLab