From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtp49.i.mail.ru (smtp49.i.mail.ru [94.100.177.109]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 2A4004696C6 for ; Mon, 20 Apr 2020 01:25:16 +0300 (MSK) From: Leonid Vasiliev Date: Mon, 20 Apr 2020 01:25:05 +0300 Message-Id: <626bc8fc9b9942648d2d40beb905eff2726cd0e6.1587334824.git.lvasiliev@tarantool.org> In-Reply-To: References: In-Reply-To: References: Subject: [Tarantool-patches] [PATCH V6 03/10] error: add session setting for error type marshaling List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: v.shpilevoy@tarantool.org, alexander.turenko@tarantool.org Cc: tarantool-patches@dev.tarantool.org Errors are encoded as a string when serialized to MessagePack to be sent over IProto or when just saved into a buffer via Lua modules msgpackffi and msgpack. That is not very useful on client-side, because most of the error metadata is lost: code, type, trace - everything except the message. Next commits are going to dedicate a new MP_EXT type to error objects so as everything could be encoded, and on client side it would be possible to restore types. But this is a breaking change in case some users use old connectors when work with newer Tarantool instances. So to smooth the upgrade there is a new session setting - 'error_marshaling_enabled'. By default it is false. When it is true, all fibers of the given session will serialize error objects as MP_EXT. Co-authored-by: Vladislav Shpilevoy Needed for #4398 --- src/box/lua/call.c | 25 ++++++++++-------- src/box/lua/execute.c | 2 +- src/box/lua/tuple.c | 12 ++++----- src/box/session.cc | 5 +++- src/box/session.h | 5 ++-- src/box/session_settings.c | 56 ++++++++++++++++++++++++++++++++++++++++ src/box/session_settings.h | 1 + src/box/sql/func.c | 4 ++- src/lua/msgpack.c | 25 +++++++++--------- src/lua/msgpack.h | 8 +++--- src/lua/utils.c | 11 +++++--- src/lua/utils.h | 7 +++-- src/serializer_opts.h | 44 +++++++++++++++++++++++++++++++ test/box/session_settings.result | 3 ++- 14 files changed, 164 insertions(+), 44 deletions(-) create mode 100644 src/serializer_opts.h diff --git a/src/box/lua/call.c b/src/box/lua/call.c index 1d29494..6588ec2 100644 --- a/src/box/lua/call.c +++ b/src/box/lua/call.c @@ -46,6 +46,7 @@ #include "small/obuf.h" #include "trivia/util.h" #include "mpstream/mpstream.h" +#include "box/session.h" /** * A helper to find a Lua function by name and put it @@ -174,7 +175,7 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, */ for (int i = 1; i <= nrets; ++i) { struct luaL_field field; - if (luaL_tofield(L, cfg, i, &field) < 0) + if (luaL_tofield(L, cfg, NULL, i, &field) < 0) return luaT_error(L); struct tuple *tuple; if (field.type == MP_EXT && @@ -188,11 +189,11 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, */ lua_pushvalue(L, i); mpstream_encode_array(stream, 1); - luamp_encode_r(L, cfg, stream, &field, 0); + luamp_encode_r(L, cfg, NULL, stream, &field, 0); lua_pop(L, 1); } else { /* `return ..., array, ...` */ - luamp_encode(L, cfg, stream, i); + luamp_encode(L, cfg, NULL, stream, i); } } return nrets; @@ -203,7 +204,7 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, * Inspect the first result */ struct luaL_field root; - if (luaL_tofield(L, cfg, 1, &root) < 0) + if (luaL_tofield(L, cfg, NULL, 1, &root) < 0) return luaT_error(L); struct tuple *tuple; if (root.type == MP_EXT && (tuple = luaT_istuple(L, 1)) != NULL) { @@ -217,7 +218,7 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, */ mpstream_encode_array(stream, 1); assert(lua_gettop(L) == 1); - luamp_encode_r(L, cfg, stream, &root, 0); + luamp_encode_r(L, cfg, NULL, stream, &root, 0); return 1; } @@ -233,7 +234,7 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, for (uint32_t t = 1; t <= root.size; t++) { lua_rawgeti(L, 1, t); struct luaL_field field; - if (luaL_tofield(L, cfg, -1, &field) < 0) + if (luaL_tofield(L, cfg, NULL, -1, &field) < 0) return luaT_error(L); if (field.type == MP_EXT && (tuple = luaT_istuple(L, -1))) { tuple_to_mpstream(tuple, stream); @@ -249,13 +250,13 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, * Encode the first field of tuple using * existing information from luaL_tofield */ - luamp_encode_r(L, cfg, stream, &field, 0); + luamp_encode_r(L, cfg, NULL, stream, &field, 0); lua_pop(L, 1); assert(lua_gettop(L) == 1); /* Encode remaining fields as usual */ for (uint32_t f = 2; f <= root.size; f++) { lua_rawgeti(L, 1, f); - luamp_encode(L, cfg, stream, -1); + luamp_encode(L, cfg, NULL, stream, -1); lua_pop(L, 1); } return 1; @@ -265,10 +266,10 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, * { tuple/array, ..., { scalar }, ... }` */ mpstream_encode_array(stream, 1); - luamp_encode_r(L, cfg, stream, &field, 0); + luamp_encode_r(L, cfg, NULL, stream, &field, 0); } else { /* `return { tuple/array, ..., tuple/array, ... }` */ - luamp_encode_r(L, cfg, stream, &field, 0); + luamp_encode_r(L, cfg, NULL, stream, &field, 0); } lua_pop(L, 1); assert(lua_gettop(L) == 1); @@ -392,9 +393,11 @@ encode_lua_call(lua_State *L) * TODO: forbid explicit yield from __serialize or __index here */ struct luaL_serializer *cfg = luaL_msgpack_default; + const struct serializer_opts *opts = + ¤t_session()->meta.serializer_opts; int size = lua_gettop(ctx->port->L); for (int i = 1; i <= size; ++i) - luamp_encode(ctx->port->L, cfg, ctx->stream, i); + luamp_encode(ctx->port->L, cfg, opts, ctx->stream, i); ctx->port->size = size; mpstream_flush(ctx->stream); return 0; diff --git a/src/box/lua/execute.c b/src/box/lua/execute.c index b4843b2..b5db3c9 100644 --- a/src/box/lua/execute.c +++ b/src/box/lua/execute.c @@ -328,7 +328,7 @@ lua_sql_bind_decode(struct lua_State *L, struct sql_bind *bind, int idx, int i) bind->name = NULL; bind->name_len = 0; } - if (luaL_tofield(L, luaL_msgpack_default, -1, &field) < 0) + if (luaL_tofield(L, luaL_msgpack_default, NULL, -1, &field) < 0) return -1; switch (field.type) { case MP_UINT: diff --git a/src/box/lua/tuple.c b/src/box/lua/tuple.c index 4b0701e..aba906d 100644 --- a/src/box/lua/tuple.c +++ b/src/box/lua/tuple.c @@ -120,7 +120,7 @@ luaT_tuple_new(struct lua_State *L, int idx, box_tuple_format_t *format) int argc = lua_gettop(L); mpstream_encode_array(&stream, argc); for (int k = 1; k <= argc; ++k) { - luamp_encode(L, luaL_msgpack_default, &stream, k); + luamp_encode(L, luaL_msgpack_default, NULL, &stream, k); } } else { /* Create the tuple from a Lua table. */ @@ -252,18 +252,18 @@ luamp_convert_key(struct lua_State *L, struct luaL_serializer *cfg, return tuple_to_mpstream(tuple, stream); struct luaL_field field; - if (luaL_tofield(L, cfg, index, &field) < 0) + if (luaL_tofield(L, cfg, NULL, index, &field) < 0) luaT_error(L); if (field.type == MP_ARRAY) { lua_pushvalue(L, index); - luamp_encode_r(L, cfg, stream, &field, 0); + luamp_encode_r(L, cfg, NULL, stream, &field, 0); lua_pop(L, 1); } else if (field.type == MP_NIL) { mpstream_encode_array(stream, 0); } else { mpstream_encode_array(stream, 1); lua_pushvalue(L, index); - luamp_encode_r(L, cfg, stream, &field, 0); + luamp_encode_r(L, cfg, NULL, stream, &field, 0); lua_pop(L, 1); } } @@ -275,7 +275,7 @@ luamp_encode_tuple(struct lua_State *L, struct luaL_serializer *cfg, struct tuple *tuple = luaT_istuple(L, index); if (tuple != NULL) { return tuple_to_mpstream(tuple, stream); - } else if (luamp_encode(L, cfg, stream, index) != MP_ARRAY) { + } else if (luamp_encode(L, cfg, NULL, stream, index) != MP_ARRAY) { diag_set(ClientError, ER_TUPLE_NOT_ARRAY); luaT_error(L); } @@ -435,7 +435,7 @@ lbox_tuple_transform(struct lua_State *L) mpstream_encode_array(&stream, 3); mpstream_encode_str(&stream, "!"); mpstream_encode_uint(&stream, offset); - luamp_encode(L, luaL_msgpack_default, &stream, i); + luamp_encode(L, luaL_msgpack_default, NULL, &stream, i); } mpstream_flush(&stream); diff --git a/src/box/session.cc b/src/box/session.cc index b557eed..08a1092 100644 --- a/src/box/session.cc +++ b/src/box/session.cc @@ -275,6 +275,9 @@ session_find(uint64_t sid) mh_i64ptr_node(session_registry, k)->val; } +extern "C" void +session_settings_init(void); + void session_init() { @@ -283,7 +286,7 @@ session_init() panic("out of memory"); mempool_create(&session_pool, &cord()->slabc, sizeof(struct session)); credentials_create(&admin_credentials, admin_user); - sql_session_settings_init(); + session_settings_init(); } void diff --git a/src/box/session.h b/src/box/session.h index 8693799..500a88b 100644 --- a/src/box/session.h +++ b/src/box/session.h @@ -36,13 +36,12 @@ #include "fiber.h" #include "user.h" #include "authentication.h" +#include "serializer_opts.h" #if defined(__cplusplus) extern "C" { #endif /* defined(__cplusplus) */ -extern void sql_session_settings_init(); - struct port; struct session_vtab; @@ -90,6 +89,8 @@ struct session_meta { }; /** Console output format. */ enum output_format output_format; + /** Session-specific serialization options. */ + struct serializer_opts serializer_opts; }; /** diff --git a/src/box/session_settings.c b/src/box/session_settings.c index 79c4b8d..dbbbf24 100644 --- a/src/box/session_settings.c +++ b/src/box/session_settings.c @@ -42,6 +42,7 @@ struct session_setting session_settings[SESSION_SETTING_COUNT] = {}; /** Corresponding names of session settings. */ const char *session_setting_strs[SESSION_SETTING_COUNT] = { + "error_marshaling_enabled", "sql_default_engine", "sql_defer_foreign_keys", "sql_full_column_names", @@ -449,3 +450,58 @@ session_setting_find(const char *name) { else return -1; } + +/* Module independent session settings. */ + +static void +session_setting_error_marshaling_enabled_get(int id, const char **mp_pair, + const char **mp_pair_end) +{ + assert(id == SESSION_SETTING_ERROR_MARSHALING_ENABLED); + struct session *session = current_session(); + const char *name = session_setting_strs[id]; + size_t name_len = strlen(name); + bool value = session->meta.serializer_opts.error_marshaling_enabled; + size_t size = mp_sizeof_array(2) + mp_sizeof_str(name_len) + + mp_sizeof_bool(value); + + char *pos = (char*)static_alloc(size); + assert(pos != NULL); + char *pos_end = mp_encode_array(pos, 2); + pos_end = mp_encode_str(pos_end, name, name_len); + pos_end = mp_encode_bool(pos_end, value); + *mp_pair = pos; + *mp_pair_end = pos_end; +} + +static int +session_setting_error_marshaling_enabled_set(int id, const char *mp_value) +{ + assert(id == SESSION_SETTING_ERROR_MARSHALING_ENABLED); + enum mp_type mtype = mp_typeof(*mp_value); + enum field_type stype = session_settings[id].field_type; + if (mtype != MP_BOOL) { + diag_set(ClientError, ER_SESSION_SETTING_INVALID_VALUE, + session_setting_strs[id], field_type_strs[stype]); + return -1; + } + struct session *session = current_session(); + session->meta.serializer_opts.error_marshaling_enabled = + mp_decode_bool(&mp_value); + return 0; +} + +extern void +sql_session_settings_init(); + +void +session_settings_init(void) +{ + struct session_setting *s = + &session_settings[SESSION_SETTING_ERROR_MARSHALING_ENABLED]; + s->field_type = FIELD_TYPE_BOOLEAN; + s->get = session_setting_error_marshaling_enabled_get; + s->set = session_setting_error_marshaling_enabled_set; + + sql_session_settings_init(); +} diff --git a/src/box/session_settings.h b/src/box/session_settings.h index e2adc52..6560b8c 100644 --- a/src/box/session_settings.h +++ b/src/box/session_settings.h @@ -42,6 +42,7 @@ * space iterator will not be sorted properly. */ enum { + SESSION_SETTING_ERROR_MARSHALING_ENABLED, SESSION_SETTING_SQL_BEGIN, SESSION_SETTING_SQL_DEFAULT_ENGINE = SESSION_SETTING_SQL_BEGIN, SESSION_SETTING_SQL_DEFER_FOREIGN_KEYS, diff --git a/src/box/sql/func.c b/src/box/sql/func.c index e3a6e88..3768372 100644 --- a/src/box/sql/func.c +++ b/src/box/sql/func.c @@ -257,8 +257,10 @@ port_lua_get_vdbemem(struct port *base, uint32_t *size) return NULL; for (int i = 0; i < argc; i++) { struct luaL_field field; - if (luaL_tofield(L, luaL_msgpack_default, -1 - i, &field) < 0) + if (luaL_tofield(L, luaL_msgpack_default, + NULL, -1 - i, &field) < 0) { goto error; + } switch (field.type) { case MP_BOOL: mem_set_bool(&val[i], field.bval); diff --git a/src/lua/msgpack.c b/src/lua/msgpack.c index e4fb0cf..5c1bf8e 100644 --- a/src/lua/msgpack.c +++ b/src/lua/msgpack.c @@ -109,8 +109,8 @@ luamp_set_decode_extension(luamp_decode_extension_f handler) enum mp_type luamp_encode_r(struct lua_State *L, struct luaL_serializer *cfg, - struct mpstream *stream, struct luaL_field *field, - int level) + const struct serializer_opts *opts, struct mpstream *stream, + struct luaL_field *field, int level) { int top = lua_gettop(L); enum mp_type type; @@ -155,13 +155,13 @@ restart: /* used by MP_EXT of unidentified subtype */ lua_pushnil(L); /* first key */ while (lua_next(L, top) != 0) { lua_pushvalue(L, -2); /* push a copy of key to top */ - if (luaL_tofield(L, cfg, lua_gettop(L), field) < 0) + if (luaL_tofield(L, cfg, opts, lua_gettop(L), field) < 0) return luaT_error(L); - luamp_encode_r(L, cfg, stream, field, level + 1); + luamp_encode_r(L, cfg, opts, stream, field, level + 1); lua_pop(L, 1); /* pop a copy of key */ - if (luaL_tofield(L, cfg, lua_gettop(L), field) < 0) + if (luaL_tofield(L, cfg, opts, lua_gettop(L), field) < 0) return luaT_error(L); - luamp_encode_r(L, cfg, stream, field, level + 1); + luamp_encode_r(L, cfg, opts, stream, field, level + 1); lua_pop(L, 1); /* pop value */ } assert(lua_gettop(L) == top); @@ -180,9 +180,9 @@ restart: /* used by MP_EXT of unidentified subtype */ mpstream_encode_array(stream, size); for (uint32_t i = 0; i < size; i++) { lua_rawgeti(L, top, i + 1); - if (luaL_tofield(L, cfg, top + 1, field) < 0) + if (luaL_tofield(L, cfg, opts, top + 1, field) < 0) return luaT_error(L); - luamp_encode_r(L, cfg, stream, field, level + 1); + luamp_encode_r(L, cfg, opts, stream, field, level + 1); lua_pop(L, 1); } assert(lua_gettop(L) == top); @@ -213,7 +213,8 @@ restart: /* used by MP_EXT of unidentified subtype */ enum mp_type luamp_encode(struct lua_State *L, struct luaL_serializer *cfg, - struct mpstream *stream, int index) + const struct serializer_opts *opts, struct mpstream *stream, + int index) { int top = lua_gettop(L); if (index < 0) @@ -225,9 +226,9 @@ luamp_encode(struct lua_State *L, struct luaL_serializer *cfg, } struct luaL_field field; - if (luaL_tofield(L, cfg, lua_gettop(L), &field) < 0) + if (luaL_tofield(L, cfg, opts, lua_gettop(L), &field) < 0) return luaT_error(L); - enum mp_type top_type = luamp_encode_r(L, cfg, stream, &field, 0); + enum mp_type top_type = luamp_encode_r(L, cfg, opts, stream, &field, 0); if (!on_top) { lua_remove(L, top + 1); /* remove a value copy */ @@ -369,7 +370,7 @@ lua_msgpack_encode(lua_State *L) mpstream_init(&stream, buf, ibuf_reserve_cb, ibuf_alloc_cb, luamp_error, L); - luamp_encode(L, cfg, &stream, 1); + luamp_encode(L, cfg, NULL, &stream, 1); mpstream_flush(&stream); if (index > 1) { diff --git a/src/lua/msgpack.h b/src/lua/msgpack.h index d0ade30..5a91e28 100644 --- a/src/lua/msgpack.h +++ b/src/lua/msgpack.h @@ -43,6 +43,7 @@ extern "C" { struct luaL_serializer; struct mpstream; +struct serializer_opts; /** * Default instance of msgpack serializer (msgpack = require('msgpack')). @@ -63,12 +64,13 @@ enum { LUAMP_ALLOC_FACTOR = 256 }; /* low-level function needed for execute_lua_call() */ enum mp_type luamp_encode_r(struct lua_State *L, struct luaL_serializer *cfg, - struct mpstream *stream, struct luaL_field *field, - int level); + const struct serializer_opts *opts, struct mpstream *stream, + struct luaL_field *field, int level); enum mp_type luamp_encode(struct lua_State *L, struct luaL_serializer *cfg, - struct mpstream *stream, int index); + const struct serializer_opts *opts, struct mpstream *stream, + int index); void luamp_decode(struct lua_State *L, struct luaL_serializer *cfg, diff --git a/src/lua/utils.c b/src/lua/utils.c index 667365f..42a226c 100644 --- a/src/lua/utils.c +++ b/src/lua/utils.c @@ -452,7 +452,7 @@ lua_field_inspect_ucdata(struct lua_State *L, struct luaL_serializer *cfg, lua_pcall(L, 1, 1, 0); /* replace obj with the unpacked value */ lua_replace(L, idx); - if (luaL_tofield(L, cfg, idx, field) < 0) + if (luaL_tofield(L, cfg, NULL, idx, field) < 0) luaT_error(L); } /* else ignore lua_gettable exceptions */ lua_settop(L, top); /* remove temporary objects */ @@ -515,7 +515,7 @@ lua_field_try_serialize(struct lua_State *L) /* copy object itself */ lua_pushvalue(L, 1); lua_call(L, 1, 1); - s->is_error = (luaL_tofield(L, cfg, -1, field) != 0); + s->is_error = (luaL_tofield(L, cfg, NULL, -1, field) != 0); s->is_value_returned = true; return 1; } @@ -641,14 +641,17 @@ lua_field_tostring(struct lua_State *L, struct luaL_serializer *cfg, int idx, lua_call(L, 1, 1); lua_replace(L, idx); lua_settop(L, top); - if (luaL_tofield(L, cfg, idx, field) < 0) + if (luaL_tofield(L, cfg, NULL, idx, field) < 0) luaT_error(L); } int -luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, int index, +luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, + const struct serializer_opts *opts, int index, struct luaL_field *field) { + /* opts will be used for encode MP_ERROR in the future */ + (void)opts; if (index < 0) index = lua_gettop(L) + index + 1; diff --git a/src/lua/utils.h b/src/lua/utils.h index 4bc0417..b10754e 100644 --- a/src/lua/utils.h +++ b/src/lua/utils.h @@ -59,6 +59,7 @@ extern "C" { #include "lib/core/mp_extension_types.h" #include "lib/core/decimal.h" /* decimal_t */ +struct serializer_opts; struct lua_State; struct ibuf; struct tt_uuid; @@ -366,6 +367,7 @@ struct luaL_field { * * @param L stack * @param cfg configuration + * @param opts the Lua serializer additional options. * @param index stack index * @param field conversion result * @@ -373,7 +375,8 @@ struct luaL_field { * @retval -1 Error. */ int -luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, int index, +luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, + const struct serializer_opts *opts, int index, struct luaL_field *field); /** @@ -412,7 +415,7 @@ static inline void luaL_checkfield(struct lua_State *L, struct luaL_serializer *cfg, int idx, struct luaL_field *field) { - if (luaL_tofield(L, cfg, idx, field) < 0) + if (luaL_tofield(L, cfg, NULL, idx, field) < 0) luaT_error(L); if (field->type != MP_EXT || field->ext_type != MP_UNKNOWN_EXTENSION) return; diff --git a/src/serializer_opts.h b/src/serializer_opts.h new file mode 100644 index 0000000..9e2c15e --- /dev/null +++ b/src/serializer_opts.h @@ -0,0 +1,44 @@ +#pragma once +/* + * Copyright 2010-2020, 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. + */ + +/** + * Serializer options which can regulate how to serialize + * something in scope of one session. + */ +struct serializer_opts { + /** + * When enabled, error objects get their own MP_EXT + * MessagePack type and therefore can be type-safely + * transmitted over the network. + */ + bool error_marshaling_enabled; +}; diff --git a/test/box/session_settings.result b/test/box/session_settings.result index ea6302d..149cc4b 100644 --- a/test/box/session_settings.result +++ b/test/box/session_settings.result @@ -52,7 +52,8 @@ s:replace({'sql_defer_foreign_keys', true}) -- s:select() | --- - | - - ['sql_default_engine', 'memtx'] + | - - ['error_marshaling_enabled', false] + | - ['sql_default_engine', 'memtx'] | - ['sql_defer_foreign_keys', false] | - ['sql_full_column_names', false] | - ['sql_full_metadata', false] -- 2.7.4