From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from localhost (localhost [127.0.0.1]) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTP id 9B50C2AC24 for ; Tue, 26 Mar 2019 06:59:35 -0400 (EDT) Received: from turing.freelists.org ([127.0.0.1]) by localhost (turing.freelists.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id Dp_21ZOI1I9h for ; Tue, 26 Mar 2019 06:59:35 -0400 (EDT) Received: from smtp32.i.mail.ru (smtp32.i.mail.ru [94.100.177.92]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTPS id 2E5072AC53 for ; Tue, 26 Mar 2019 06:59:35 -0400 (EDT) Subject: [tarantool-patches] Re: [PATCH v2 8/9] box: exported sql_bind structure and API References: <1c364cb9d4584a5c7d1375a121f794a680efbc3f.1548838034.git.kshcherbatov@tarantool.org> From: Kirill Shcherbatov Message-ID: <82086256-e55e-5583-546c-5f9c872b27bc@tarantool.org> Date: Tue, 26 Mar 2019 13:59:33 +0300 MIME-Version: 1.0 In-Reply-To: <1c364cb9d4584a5c7d1375a121f794a680efbc3f.1548838034.git.kshcherbatov@tarantool.org> Content-Type: text/plain; charset=utf-8 Content-Language: en-US Content-Transfer-Encoding: 7bit Sender: tarantool-patches-bounce@freelists.org Errors-to: tarantool-patches-bounce@freelists.org Reply-To: tarantool-patches@freelists.org List-Help: List-Unsubscribe: List-software: Ecartis version 1.0.0 List-Id: tarantool-patches List-Subscribe: List-Owner: List-post: List-Archive: To: tarantool-patches@freelists.org, korablev@tarantool.org Exported sql_bind structure, sql_bind_decode, sql_bind_column and sql_bind routines in separate module bind.h. We need SQL bindings in further pathes with check constraints. Needed for #3691 --- src/box/CMakeLists.txt | 1 + src/box/bind.c | 239 ++++++++++++++++++++++++++++++++++++ src/box/bind.h | 136 +++++++++++++++++++++ src/box/execute.c | 270 +---------------------------------------- src/box/execute.h | 15 --- src/box/iproto.cc | 1 + 6 files changed, 378 insertions(+), 284 deletions(-) create mode 100644 src/box/bind.c create mode 100644 src/box/bind.h diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt index 070d4a9fe..8ea9afcdd 100644 --- a/src/box/CMakeLists.txt +++ b/src/box/CMakeLists.txt @@ -117,6 +117,7 @@ add_library(box STATIC journal.c ck_constraint.c sql.c + bind.c execute.c wal.c call.c diff --git a/src/box/bind.c b/src/box/bind.c new file mode 100644 index 000000000..fb73cf44d --- /dev/null +++ b/src/box/bind.c @@ -0,0 +1,239 @@ +/* + * Copyright 2010-2019, Tarantool AUTHORS, please see AUTHORS file. + * + * 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 ``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 + * 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 "bind.h" +#include "errcode.h" +#include "small/region.h" +#include "sql/sqlInt.h" +#include "sql/sqlLimit.h" +#include "sql/vdbe.h" + +const char *sql_type_strs[] = { + NULL, + "INTEGER", + "FLOAT", + "TEXT", + "BLOB", + "NULL", +}; + +/** + * Return a string name of a parameter marker. + * @param Bind to get name. + * @retval Zero terminated name. + */ +static inline const char * +sql_bind_name(const struct sql_bind *bind) +{ + if (bind->name) + return tt_sprintf("'%.*s'", bind->name_len, bind->name); + else + return tt_sprintf("%d", (int) bind->pos); +} + +int +sql_bind_decode(struct sql_bind *bind, int i, const char **packet) +{ + bind->pos = i + 1; + if (mp_typeof(**packet) == MP_MAP) { + uint32_t len = mp_decode_map(packet); + /* + * A named parameter is an MP_MAP with + * one key - {'name': value}. + * Report parse error otherwise. + */ + if (len != 1 || mp_typeof(**packet) != MP_STR) { + diag_set(ClientError, ER_INVALID_MSGPACK, + "SQL bind parameter"); + return -1; + } + bind->name = mp_decode_str(packet, &bind->name_len); + } else { + bind->name = NULL; + bind->name_len = 0; + } + switch (mp_typeof(**packet)) { + case MP_UINT: { + uint64_t n = mp_decode_uint(packet); + if (n > INT64_MAX) { + diag_set(ClientError, ER_SQL_BIND_VALUE, + sql_bind_name(bind), "INTEGER"); + return -1; + } + bind->i64 = (int64_t) n; + bind->type = SQL_INTEGER; + bind->bytes = sizeof(bind->i64); + break; + } + case MP_INT: + bind->i64 = mp_decode_int(packet); + bind->type = SQL_INTEGER; + bind->bytes = sizeof(bind->i64); + break; + case MP_STR: + bind->s = mp_decode_str(packet, &bind->bytes); + bind->type = SQL_TEXT; + break; + case MP_DOUBLE: + bind->d = mp_decode_double(packet); + bind->type = SQL_FLOAT; + bind->bytes = sizeof(bind->d); + break; + case MP_FLOAT: + bind->d = mp_decode_float(packet); + bind->type = SQL_FLOAT; + bind->bytes = sizeof(bind->d); + break; + case MP_NIL: + mp_decode_nil(packet); + bind->type = SQL_NULL; + bind->bytes = 1; + break; + case MP_BOOL: + /* sql doesn't support boolean. Use int instead. */ + bind->i64 = mp_decode_bool(packet) ? 1 : 0; + bind->type = SQL_INTEGER; + bind->bytes = sizeof(bind->i64); + break; + case MP_BIN: + bind->s = mp_decode_bin(packet, &bind->bytes); + bind->type = SQL_BLOB; + break; + case MP_EXT: + bind->s = *packet; + mp_next(packet); + bind->bytes = *packet - bind->s; + bind->type = SQL_BLOB; + break; + case MP_ARRAY: + diag_set(ClientError, ER_SQL_BIND_TYPE, "ARRAY", + sql_bind_name(bind)); + return -1; + case MP_MAP: + diag_set(ClientError, ER_SQL_BIND_TYPE, "MAP", + sql_bind_name(bind)); + return -1; + default: + unreachable(); + } + return 0; +} + +int +sql_bind_list_decode(const char *data, struct sql_bind **out_bind) +{ + assert(data != NULL); + if (mp_typeof(*data) != MP_ARRAY) { + diag_set(ClientError, ER_INVALID_MSGPACK, "SQL parameter list"); + return -1; + } + uint32_t bind_count = mp_decode_array(&data); + if (bind_count == 0) + return 0; + if (bind_count > SQL_BIND_PARAMETER_MAX) { + diag_set(ClientError, ER_SQL_BIND_PARAMETER_MAX, + (int) bind_count); + return -1; + } + struct region *region = &fiber()->gc; + uint32_t used = region_used(region); + size_t size = sizeof(struct sql_bind) * bind_count; + struct sql_bind *bind = (struct sql_bind *) region_alloc(region, size); + if (bind == NULL) { + diag_set(OutOfMemory, size, "region_alloc", "struct sql_bind"); + return -1; + } + for (uint32_t i = 0; i < bind_count; ++i) { + if (sql_bind_decode(&bind[i], i, &data) != 0) { + region_truncate(region, used); + return -1; + } + } + *out_bind = bind; + return bind_count; +} + +int +sql_bind_column(struct sql_stmt *stmt, const struct sql_bind *p, + uint32_t pos) +{ + int rc; + if (p->name != NULL) { + pos = sql_bind_parameter_lindex(stmt, p->name, p->name_len); + if (pos == 0) { + diag_set(ClientError, ER_SQL_BIND_NOT_FOUND, + sql_bind_name(p)); + return -1; + } + } + switch (p->type) { + case SQL_INTEGER: + rc = sql_bind_int64(stmt, pos, p->i64); + break; + case SQL_FLOAT: + rc = sql_bind_double(stmt, pos, p->d); + break; + case SQL_TEXT: + /* + * Parameters are allocated within message pack, + * received from the iproto thread. IProto thread + * now is waiting for the response and it will not + * free the packet until sql_finalize. So + * there is no need to copy the packet and we can + * use SQL_STATIC. + */ + rc = sql_bind_text64(stmt, pos, p->s, p->bytes, + SQL_STATIC); + break; + case SQL_NULL: + rc = sql_bind_null(stmt, pos); + break; + case SQL_BLOB: + rc = sql_bind_blob64(stmt, pos, (const void *) p->s, + p->bytes, SQL_STATIC); + break; + default: + unreachable(); + } + if (rc == SQL_OK) + return 0; + + switch (rc) { + case SQL_NOMEM: + diag_set(OutOfMemory, p->bytes, "vdbe", "bind value"); + break; + case SQL_TOOBIG: + default: + diag_set(ClientError, ER_SQL_BIND_VALUE, sql_bind_name(p), + sql_type_strs[p->type]); + break; + } + return -1; +} diff --git a/src/box/bind.h b/src/box/bind.h new file mode 100644 index 000000000..4414ea793 --- /dev/null +++ b/src/box/bind.h @@ -0,0 +1,136 @@ +#ifndef TARANTOOL_SQL_BIND_H_INCLUDED +#define TARANTOOL_SQL_BIND_H_INCLUDED +/* + * Copyright 2010-2019, Tarantool AUTHORS, please see AUTHORS file. + * + * 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 ``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 + * 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. + */ + +#if defined(__cplusplus) +extern "C" { +#endif + +#include +#include +#include + +struct sql_stmt; + +/** + * Name and value of an SQL prepared statement parameter. + * @todo: merge with sql_value. + */ +struct sql_bind { + /** Bind name. NULL for ordinal binds. */ + const char *name; + /** Length of the @name. */ + uint32_t name_len; + /** Ordinal position of the bind, for ordinal binds. */ + uint32_t pos; + + /** Byte length of the value. */ + uint32_t bytes; + /** SQL type of the value. */ + uint8_t type; + /** Bind value. */ + union { + double d; + int64_t i64; + /** For string or blob. */ + const char *s; + }; +}; + +/** + * Parse MessagePack array of SQL parameters. + * @param data MessagePack array of parameters. Each parameter + * either must have scalar type, or must be a map with the + * following format: {name: value}. Name - string name of + * the named parameter, value - scalar value of the + * parameter. Named and positioned parameters can be mixed. + * @param[out] out_bind Pointer to save decoded parameters. + * + * @retval >= 0 Number of decoded parameters. + * @retval -1 Client or memory error. + */ +int +sql_bind_list_decode(const char *data, struct sql_bind **out_bind); + +/** + * Decode a single bind column from the binary protocol packet. + * @param[out] bind Bind to decode to. + * @param i Ordinal bind number. + * @param packet MessagePack encoded parameter value. Either + * scalar or map: {string_name: scalar_value}. + * + * @retval 0 Success. + * @retval -1 Memory or client error. + */ +int +sql_bind_decode(struct sql_bind *bind, int i, const char **packet); + +/** + * Bind SQL parameter value to its position. + * @param stmt Prepared statement. + * @param p Parameter value. + * @param pos Ordinal bind position. + * + * @retval 0 Success. + * @retval -1 SQL error. + */ +int +sql_bind_column(struct sql_stmt *stmt, const struct sql_bind *p, + uint32_t pos); + +/** + * Bind parameter values to the prepared statement. + * @param stmt Prepared statement. + * @param bind Parameters to bind. + * @param bind_count Length of @a bind. + * + * @retval 0 Success. + * @retval -1 Client or memory error. + */ +static inline int +sql_bind(struct sql_stmt *stmt, const struct sql_bind *bind, + uint32_t bind_count) +{ + assert(stmt != NULL); + uint32_t pos = 1; + for (uint32_t i = 0; i < bind_count; pos = ++i + 1) { + if (sql_bind_column(stmt, &bind[i], pos) != 0) + return -1; + } + return 0; +} + +#if defined(__cplusplus) +} /* extern "C" { */ +#endif + +#endif /* TARANTOOL_SQL_BIND_H_INCLUDED */ diff --git a/src/box/execute.c b/src/box/execute.c index 7c77df2e5..4da2fe74a 100644 --- a/src/box/execute.c +++ b/src/box/execute.c @@ -30,6 +30,7 @@ */ #include "execute.h" +#include "bind.h" #include "iproto_constants.h" #include "sql/sqlInt.h" #include "sql/sqlLimit.h" @@ -44,190 +45,10 @@ #include "tuple.h" #include "sql/vdbe.h" -const char *sql_type_strs[] = { - NULL, - "INTEGER", - "FLOAT", - "TEXT", - "BLOB", - "NULL", -}; - const char *sql_info_key_strs[] = { "row count", }; -/** - * Name and value of an SQL prepared statement parameter. - * @todo: merge with sql_value. - */ -struct sql_bind { - /** Bind name. NULL for ordinal binds. */ - const char *name; - /** Length of the @name. */ - uint32_t name_len; - /** Ordinal position of the bind, for ordinal binds. */ - uint32_t pos; - - /** Byte length of the value. */ - uint32_t bytes; - /** SQL type of the value. */ - uint8_t type; - /** Bind value. */ - union { - double d; - int64_t i64; - /** For string or blob. */ - const char *s; - }; -}; - -/** - * Return a string name of a parameter marker. - * @param Bind to get name. - * @retval Zero terminated name. - */ -static inline const char * -sql_bind_name(const struct sql_bind *bind) -{ - if (bind->name) - return tt_sprintf("'%.*s'", bind->name_len, bind->name); - else - return tt_sprintf("%d", (int) bind->pos); -} - -/** - * Decode a single bind column from the binary protocol packet. - * @param[out] bind Bind to decode to. - * @param i Ordinal bind number. - * @param packet MessagePack encoded parameter value. Either - * scalar or map: {string_name: scalar_value}. - * - * @retval 0 Success. - * @retval -1 Memory or client error. - */ -static inline int -sql_bind_decode(struct sql_bind *bind, int i, const char **packet) -{ - bind->pos = i + 1; - if (mp_typeof(**packet) == MP_MAP) { - uint32_t len = mp_decode_map(packet); - /* - * A named parameter is an MP_MAP with - * one key - {'name': value}. - * Report parse error otherwise. - */ - if (len != 1 || mp_typeof(**packet) != MP_STR) { - diag_set(ClientError, ER_INVALID_MSGPACK, - "SQL bind parameter"); - return -1; - } - bind->name = mp_decode_str(packet, &bind->name_len); - } else { - bind->name = NULL; - bind->name_len = 0; - } - switch (mp_typeof(**packet)) { - case MP_UINT: { - uint64_t n = mp_decode_uint(packet); - if (n > INT64_MAX) { - diag_set(ClientError, ER_SQL_BIND_VALUE, - sql_bind_name(bind), "INTEGER"); - return -1; - } - bind->i64 = (int64_t) n; - bind->type = SQL_INTEGER; - bind->bytes = sizeof(bind->i64); - break; - } - case MP_INT: - bind->i64 = mp_decode_int(packet); - bind->type = SQL_INTEGER; - bind->bytes = sizeof(bind->i64); - break; - case MP_STR: - bind->s = mp_decode_str(packet, &bind->bytes); - bind->type = SQL_TEXT; - break; - case MP_DOUBLE: - bind->d = mp_decode_double(packet); - bind->type = SQL_FLOAT; - bind->bytes = sizeof(bind->d); - break; - case MP_FLOAT: - bind->d = mp_decode_float(packet); - bind->type = SQL_FLOAT; - bind->bytes = sizeof(bind->d); - break; - case MP_NIL: - mp_decode_nil(packet); - bind->type = SQL_NULL; - bind->bytes = 1; - break; - case MP_BOOL: - /* sql doesn't support boolean. Use int instead. */ - bind->i64 = mp_decode_bool(packet) ? 1 : 0; - bind->type = SQL_INTEGER; - bind->bytes = sizeof(bind->i64); - break; - case MP_BIN: - bind->s = mp_decode_bin(packet, &bind->bytes); - bind->type = SQL_BLOB; - break; - case MP_EXT: - bind->s = *packet; - mp_next(packet); - bind->bytes = *packet - bind->s; - bind->type = SQL_BLOB; - break; - case MP_ARRAY: - diag_set(ClientError, ER_SQL_BIND_TYPE, "ARRAY", - sql_bind_name(bind)); - return -1; - case MP_MAP: - diag_set(ClientError, ER_SQL_BIND_TYPE, "MAP", - sql_bind_name(bind)); - return -1; - default: - unreachable(); - } - return 0; -} - -int -sql_bind_list_decode(const char *data, struct sql_bind **out_bind) -{ - assert(data != NULL); - if (mp_typeof(*data) != MP_ARRAY) { - diag_set(ClientError, ER_INVALID_MSGPACK, "SQL parameter list"); - return -1; - } - uint32_t bind_count = mp_decode_array(&data); - if (bind_count == 0) - return 0; - if (bind_count > SQL_BIND_PARAMETER_MAX) { - diag_set(ClientError, ER_SQL_BIND_PARAMETER_MAX, - (int) bind_count); - return -1; - } - struct region *region = &fiber()->gc; - uint32_t used = region_used(region); - size_t size = sizeof(struct sql_bind) * bind_count; - struct sql_bind *bind = (struct sql_bind *) region_alloc(region, size); - if (bind == NULL) { - diag_set(OutOfMemory, size, "region_alloc", "struct sql_bind"); - return -1; - } - for (uint32_t i = 0; i < bind_count; ++i) { - if (sql_bind_decode(&bind[i], i, &data) != 0) { - region_truncate(region, used); - return -1; - } - } - *out_bind = bind; - return bind_count; -} - /** * Serialize a single column of a result set row. * @param stmt Prepared and started statement. At least one @@ -363,95 +184,6 @@ error: return -1; } -/** - * Bind SQL parameter value to its position. - * @param stmt Prepared statement. - * @param p Parameter value. - * @param pos Ordinal bind position. - * - * @retval 0 Success. - * @retval -1 SQL error. - */ -static inline int -sql_bind_column(struct sql_stmt *stmt, const struct sql_bind *p, - uint32_t pos) -{ - int rc; - if (p->name != NULL) { - pos = sql_bind_parameter_lindex(stmt, p->name, p->name_len); - if (pos == 0) { - diag_set(ClientError, ER_SQL_BIND_NOT_FOUND, - sql_bind_name(p)); - return -1; - } - } - switch (p->type) { - case SQL_INTEGER: - rc = sql_bind_int64(stmt, pos, p->i64); - break; - case SQL_FLOAT: - rc = sql_bind_double(stmt, pos, p->d); - break; - case SQL_TEXT: - /* - * Parameters are allocated within message pack, - * received from the iproto thread. IProto thread - * now is waiting for the response and it will not - * free the packet until sql_finalize. So - * there is no need to copy the packet and we can - * use SQL_STATIC. - */ - rc = sql_bind_text64(stmt, pos, p->s, p->bytes, - SQL_STATIC); - break; - case SQL_NULL: - rc = sql_bind_null(stmt, pos); - break; - case SQL_BLOB: - rc = sql_bind_blob64(stmt, pos, (const void *) p->s, - p->bytes, SQL_STATIC); - break; - default: - unreachable(); - } - if (rc == SQL_OK) - return 0; - - switch (rc) { - case SQL_NOMEM: - diag_set(OutOfMemory, p->bytes, "vdbe", "bind value"); - break; - case SQL_TOOBIG: - default: - diag_set(ClientError, ER_SQL_BIND_VALUE, sql_bind_name(p), - sql_type_strs[p->type]); - break; - } - return -1; -} - -/** - * Bind parameter values to the prepared statement. - * @param stmt Prepared statement. - * @param bind Parameters to bind. - * @param bind_count Length of @a bind. - * - * @retval 0 Success. - * @retval -1 Client or memory error. - */ -static inline int -sql_bind(struct sql_stmt *stmt, const struct sql_bind *bind, - uint32_t bind_count) -{ - assert(stmt != NULL); - uint32_t pos = 1; - for (uint32_t i = 0; i < bind_count; pos = ++i + 1) { - if (sql_bind_column(stmt, &bind[i], pos) != 0) - return -1; - } - return 0; -} - /** * Serialize a description of the prepared statement. * @param stmt Prepared statement. diff --git a/src/box/execute.h b/src/box/execute.h index 12d893a73..08bbbf5bf 100644 --- a/src/box/execute.h +++ b/src/box/execute.h @@ -60,21 +60,6 @@ struct sql_response { void *prep_stmt; }; -/** - * Parse MessagePack array of SQL parameters. - * @param data MessagePack array of parameters. Each parameter - * either must have scalar type, or must be a map with the - * following format: {name: value}. Name - string name of - * the named parameter, value - scalar value of the - * parameter. Named and positioned parameters can be mixed. - * @param[out] out_bind Pointer to save decoded parameters. - * - * @retval >= 0 Number of decoded parameters. - * @retval -1 Client or memory error. - */ -int -sql_bind_list_decode(const char *data, struct sql_bind **out_bind); - /** * Dump a built response into @an out buffer. The response is * destroyed. diff --git a/src/box/iproto.cc b/src/box/iproto.cc index 3b0ba6234..0a1a73554 100644 --- a/src/box/iproto.cc +++ b/src/box/iproto.cc @@ -49,6 +49,7 @@ #include "memory.h" #include "random.h" +#include "bind.h" #include "port.h" #include "box.h" #include "call.h" -- 2.21.0