[Tarantool-patches] [PATCH 2/3] box: introduce _session_settings system space

Mergen Imeev imeevma at tarantool.org
Thu Dec 26 21:01:16 MSK 2019


Thank you for review and fixes! I haven't found any issues
with them. New patch below.

On Sat, Dec 21, 2019 at 06:59:12PM +0100, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> I've pushed my review fixes on top of this commit. See it below
> and on the branch. If you agree, then squash. Otherwise lets
> discuss. There are lots of fixes, so review carefully. In my
> commit you can find inlined explanations about some changes.
> 
> Also see 3 comments below inlined into your commit.
> 
> During the review I also thought more about how to implement setting
> modules - in a single big namespace of all session settings, each
> with individual session settings, or like I proposed and you've done
> here, with modules approach.
> 
> Looks like I was wrong, and the only extendible option is to have a
> single namespace, sorry. This is because not all settings can be split
> into submodules and not all of them will have the same prefix. For
> example, setting 'read_only'. I can't find a good prefix for it.
> Additionally, some SQL settings may migrate down to box in future, such
> as foreign key- and trigger- related. And they also likely not to have
> one prefix.
> 
> Nonetheless, I've decided that we can move settings to one namespace
> later, as a separate simple ticket. Here we will finish public API.
> 
> We can add individual settings in the current solution too, but it will
> look like a module with a single setting.
> 
> Also we need to optimize search. Currently it is fullscan what is
> not acceptable when we have a sorted array. But also, a subject for a
> separate issue.
> 
> On 19/12/2019 09:32, imeevma at tarantool.org wrote:
> > This patch creates _session_settings system space. This space is
> > used to view and change session settings. There are no settings at
> > the moment, some will be added in the next patch.
> > 
> > Part of #4511
> > ---
> >  src/box/CMakeLists.txt                             |   1 +
> >  src/box/bootstrap.snap                             | Bin 5921 -> 5975 bytes
> >  src/box/lua/space.cc                               |   2 +
> >  src/box/lua/upgrade.lua                            |  15 +
> >  src/box/schema_def.h                               |   8 +
> >  src/box/session_settings.c                         | 409 +++++++++++++++++++++
> >  src/box/session_settings.h                         |  61 +++
> >  src/box/virtual_engine.c                           |  52 ++-
> >  test/app-tap/tarantoolctl.test.lua                 |   4 +-
> >  test/box-py/bootstrap.result                       |   3 +
> >  test/box/access_sysview.result                     |   6 +-
> >  test/box/alter.result                              |   5 +-
> >  ...h-4511-access-settings-from-any-frontend.result | 108 +++++-
> >  ...4511-access-settings-from-any-frontend.test.lua |  37 ++
> >  test/wal_off/alter.result                          |   2 +-
> >  15 files changed, 697 insertions(+), 16 deletions(-)
> >  create mode 100644 src/box/session_settings.c
> >  create mode 100644 src/box/session_settings.h
> > 
> > diff --git a/src/box/session_settings.c b/src/box/session_settings.c
> > new file mode 100644
> > index 0000000..b5b5db8
> > --- /dev/null
> > +++ b/src/box/session_settings.c
> > @@ -0,0 +1,409 @@
> > +
> > +static int
> > +session_settings_iterator_next(struct iterator *iterator, struct tuple **result)
> > +{
> > +	struct session_settings_iterator *it =
> > +		(struct session_settings_iterator *)iterator;
> > +	if (it->module_id >= SESSION_SETTING_MODULE_max || it->module_id < 0) {
> > +		*result = NULL;
> > +		return 0;
> > +	}
> > +
> > +	struct tuple *ret = NULL;
> > +	int i = it->module_id;
> > +	int j = it->current_id;
> > +	enum iterator_type type = it->iterator_type;
> > +	struct tuple_format *format = it->format;
> > +	struct session_settings_modules *module;
> > +	const char *key = it->key;
> > +
> > +	if (!iterator_type_is_reverse(type)) {
> 
> 1. That condition never changes after iteration creation. You could
> reuse virtuality of iterator.next to calculate that condition only
> once. For that I've split that function in two: next and prev
> separately.
> 
> > +		for (; i < SESSION_SETTING_MODULE_max; ++i) {
> > +			module = &session_settings_modules[i];
> > +			if (module->get(format, j, key, type, &j, &ret) != 0)
> > +				return -1;
> > +			if (ret != NULL)
> > +				break;
> > +			j = 0;
> > +		}
> > +		*result = ret;
> > +		if (i == SESSION_SETTING_MODULE_max) {
> > +			it->module_id = SESSION_SETTING_MODULE_max;
> > +			it->current_id = 0;
> > +			return 0;
> > +		}
> > +		++j;
> > +		if (j >= (int)module->size) {
> > +			++i;
> > +			j = 0;
> > +		}
> > +		it->module_id = i;
> > +		it->current_id = j;
> > +		return 0;
> > +	}
> > +
> > +	for (; i >= 0; --i) {
> > +		module = &session_settings_modules[i];
> > +		if (module->get(format, j, key, type, &j, &ret) != 0)
> > +			return -1;
> > +		if (ret != NULL)
> > +			break;
> > +		if (i > 0)
> > +			j = session_settings_modules[i - 1].size - 1;
> > +	}
> > +	*result = ret;
> > +	if (i < 0) {
> > +		it->module_id = -1;
> > +		it->current_id = 0;
> > +		return 0;
> > +	}
> > +	--j;
> > +	if (j < 0) {
> > +		--i;
> > +		if (i >= 0)
> > +			j = module[i].size - 1;
> > +	}
> > +	it->module_id = i;
> > +	it->current_id = j;
> > +	return 0;
> > +}
> > diff --git a/src/box/session_settings.h b/src/box/session_settings.h
> > new file mode 100644
> > index 0000000..e7ecbdb
> > --- /dev/null
> > +++ b/src/box/session_settings.h
> > @@ -0,0 +1,61 @@
> > +#if defined(__cplusplus)
> > +extern "C" {
> > +#endif /* defined(__cplusplus) */
> > +
> > +enum session_setting_module {
> > +	SESSION_SETTING_MODULE_max
> 
> 2. Usually we do vice versa - lowercase enum names,
> and uppercase _MAX. That makes possible to use things
> such as STR2ENUM.
> 
> > +};
> > +
> > +struct tuple;
> > +struct tuple_format;
> > +
> > +struct session_settings_modules {
> > +	enum session_setting_module id;
> > +	uint32_t size;
> > +	int (*get)(struct tuple_format *format, int id, const char *key,
> > +		   enum iterator_type type, int *end_id, struct tuple **result);
> > +	int (*set)(struct tuple_format *format, int id, const char *value,
> > +		   struct tuple **result);
> 
> 3. The thing I mostly didn't like here is that you force submodule
> to deal with tuple format, to create tuples, to do search by key.
> That is a space's and index's task, not of SQL or other submodule.
> Moreover, that logic would be duplicated in each submodule.
> 
> I simplified this API so now the session_settings space internals do
> all the search, comparisons, tuple creation, etc. And submodules only
> provide setting name list, and API to get/set a setting by ID.
> 
> > +};
> > +
> > +extern struct session_settings_modules session_settings_modules[];
> > +extern const struct space_vtab session_settings_space_vtab;
> > +
> 
> My commit with explanations:
> 
> ==================================================================
> 
> commit 38d1392d20ee90f4bf5418e2402dd5ec589cfb1d
> Author: Vladislav Shpilevoy <v.shpilevoy at tarantool.org>
> Date:   Sat Dec 21 18:04:14 2019 +0100
> 
>     Review fixes 2/3
> 
> diff --git a/src/box/session_settings.c b/src/box/session_settings.c
> index b5b5db81e..dd1874b33 100644
> --- a/src/box/session_settings.c
> +++ b/src/box/session_settings.c
> @@ -31,35 +31,49 @@
>  #include "session_settings.h"
>  #include "xrow_update.h"
>  #include "virtual_engine.h"
> +#include "column_mask.h"
>  #include "session.h"
>  #include "schema.h"
>  #include "tuple.h"
>  #include "xrow.h"
>  #include "sql.h"
>  
> -struct session_settings_modules
> -	session_settings_modules[SESSION_SETTING_MODULE_max] = {};
> +struct session_setting_module
> +	session_setting_modules[session_setting_type_MAX] = {};
>  
>  struct session_settings_index {
>  	/** Base index. Must be the first member. */
>  	struct index base;
> -	/** Format to create tuples on the fly. */
> +	/**
> +	 * Format of the tuples iterators of this index return. It
> +	 * is stored here so as not to lookup space each time to
> +	 * get a format and create an iterator.
> +	 */
>  	struct tuple_format *format;
>  };
>  
>  struct session_settings_iterator {
>  	/** Base iterator. Must be the first member. */
>  	struct iterator base;
> -	/** Format of the tuples this iterator returns. */
> +	/**
> +	 * Format of the tuples this iterator returns. It is
> +	 * stored here so as not to lookup space each time to get
> +	 * a format for selected tuples.
> +	 */
>  	struct tuple_format *format;
> -	/** ID of current module in global list of the modules. */
> +	/**
> +	 * ID of the current session settings module in the global
> +	 * list of the modules.
> +	 */
>  	int module_id;
>  	/** ID of the setting in current module. */
> -	int current_id;
> +	int setting_id;
>  	/** Decoded key. */
>  	char *key;
> -	/** Type of iterator. */
> -	enum iterator_type iterator_type;
> +	/** True if the iterator returns only equal keys. */
> +	bool is_eq;
> +	/** True if the iterator should include equal keys. */
> +	bool is_including;
> ==================================================================
> 
> As you can see, I removed iterator type in favor of a couple of
> flags. I've found it easier to use these flags in the code than
> compare the type with all the known types each time.
> 
> ==================================================================
>  };
>  
>  static void
> @@ -72,72 +86,114 @@ session_settings_iterator_free(struct iterator *ptr)
>  }
>  
>  static int
> -session_settings_iterator_next(struct iterator *iterator, struct tuple **result)
> +session_settings_next_in_module(const struct session_setting_module *module,
> +				int *sid, const char *key, bool is_eq,
> +				bool is_including)
> ==================================================================
> 
> I've moved search by name to the session_settings space. Now
> submodules shouldn't care about it. And this is reused by all
> modules (even though now there is only one)
> 
> ==================================================================
>  {
> -	struct session_settings_iterator *it =
> -		(struct session_settings_iterator *)iterator;
> -	if (it->module_id >= SESSION_SETTING_MODULE_max || it->module_id < 0) {
> -		*result = NULL;
> +	int i = *sid;
> +	int count = module->setting_count;
> +	if (i >= count)
> +		return -1;
> +	if (key == NULL)
>  		return 0;
> +	assert(i >= 0);
> +	const char **name = &module->settings[i];
> +	for (; i < count; ++i, ++name) {
> +		int cmp = strcmp(*name, key);
> +		if ((cmp == 0 && is_including) ||
> +		    (cmp > 0 && !is_eq)) {
> +			*sid = i;
> +			return 0;
> +		}
>  	}
> +	*sid = count;
> +	return -1;
> +}
>  
> -	struct tuple *ret = NULL;
> -	int i = it->module_id;
> -	int j = it->current_id;
> -	enum iterator_type type = it->iterator_type;
> -	struct tuple_format *format = it->format;
> -	struct session_settings_modules *module;
> -	const char *key = it->key;
> -
> -	if (!iterator_type_is_reverse(type)) {
> -		for (; i < SESSION_SETTING_MODULE_max; ++i) {
> -			module = &session_settings_modules[i];
> -			if (module->get(format, j, key, type, &j, &ret) != 0)
> -				return -1;
> -			if (ret != NULL)
> -				break;
> -			j = 0;
> -		}
> -		*result = ret;
> -		if (i == SESSION_SETTING_MODULE_max) {
> -			it->module_id = SESSION_SETTING_MODULE_max;
> -			it->current_id = 0;
> +static int
> +session_settings_prev_in_module(const struct session_setting_module *module,
> +				int *sid, const char *key, bool is_eq,
> +				bool is_including)
> +{
> +	int i = *sid;
> +	int count = module->setting_count;
> +	if (i < 0)
> +		return -1;
> +	if (key == NULL)
> +		return 0;
> +	if (i >= count)
> +		i = count - 1;
> +	const char **name = &module->settings[i];
> +	for (; i >= 0; --i, --name) {
> +		int cmp = strcmp(*name, key);
> +		if ((cmp == 0 && is_including) ||
> +		    (cmp < 0 && !is_eq)) {
> +			*sid = i;
>  			return 0;
>  		}
> -		++j;
> -		if (j >= (int)module->size) {
> -			++i;
> -			j = 0;
> +	}
> +	*sid = -1;
> +	return -1;
> +}
> +
> +static int
> +session_settings_iterator_next(struct iterator *iterator, struct tuple **result)
> +{
> +	struct session_settings_iterator *it =
> +		(struct session_settings_iterator *)iterator;
> +	int mid = it->module_id, sid = it->setting_id;
> +	struct session_setting_module *module;
> +	const char *key = it->key;
> +	bool is_including = it->is_including, is_eq = it->is_eq;
> +	bool is_found = false;
> +	for (; mid < session_setting_type_MAX; ++mid, sid = 0) {
> +		module = &session_setting_modules[mid];
> +		if (session_settings_next_in_module(module, &sid, key, is_eq,
> +						    is_including) == 0) {
> +			is_found = true;
> +			break;
>  		}
> -		it->module_id = i;
> -		it->current_id = j;
> +	}
> +	it->module_id = mid;
> +	it->setting_id = sid + 1;
> +	if (!is_found) {
> +		*result = NULL;
>  		return 0;
>  	}
> +	const char *mp_pair, *mp_pair_end;
> +	module->get(sid, &mp_pair, &mp_pair_end);
> +	*result = box_tuple_new(it->format, mp_pair, mp_pair_end);
> ==================================================================
> 
> Note, I use box_tuple_new(), because iterator API assumes that a
> tuple is referenced somewhere, and that box can just bless a tuple
> to the userland. Simple tuple_new() does not work here, because it
> won't be deleted if anything will go wrong after its creation and
> before tuple_bless() in box.
> 
> ==================================================================
> +	return *result != NULL ? 0 : -1;
> +}
>  
> -	for (; i >= 0; --i) {
> -		module = &session_settings_modules[i];
> -		if (module->get(format, j, key, type, &j, &ret) != 0)
> -			return -1;
> -		if (ret != NULL)
> +static int
> +session_settings_iterator_prev(struct iterator *iterator, struct tuple **result)
> +{
> +	struct session_settings_iterator *it =
> +		(struct session_settings_iterator *)iterator;
> +	int mid = it->module_id, sid = it->setting_id;
> +	struct session_setting_module *module;
> +	const char *key = it->key;
> +	bool is_including = it->is_including, is_eq = it->is_eq;
> +	bool is_found = false;
> +	for (; mid >= 0; --mid, sid = INT_MAX) {
> +		module = &session_setting_modules[mid];
> +		if (session_settings_prev_in_module(module, &sid, key, is_eq,
> +						    is_including) == 0) {
> +			is_found = true;
>  			break;
> -		if (i > 0)
> -			j = session_settings_modules[i - 1].size - 1;
> +		}
>  	}
> -	*result = ret;
> -	if (i < 0) {
> -		it->module_id = -1;
> -		it->current_id = 0;
> +	it->module_id = mid;
> +	it->setting_id = sid - 1;
> +	if (!is_found) {
> +		*result = NULL;
>  		return 0;
>  	}
> -	--j;
> -	if (j < 0) {
> -		--i;
> -		if (i >= 0)
> -			j = module[i].size - 1;
> -	}
> -	it->module_id = i;
> -	it->current_id = j;
> -	return 0;
> +	const char *mp_pair, *mp_pair_end;
> +	module->get(sid, &mp_pair, &mp_pair_end);
> +	*result = box_tuple_new(it->format, mp_pair, mp_pair_end);
> +	return *result != NULL ? 0 : -1;
>  }
>  
>  static void
> @@ -175,15 +231,23 @@ session_settings_index_create_iterator(struct index *base,
>  		return NULL;
>  	}
>  	iterator_create(&it->base, base);
> -	it->base.next = session_settings_iterator_next;
>  	it->base.free = session_settings_iterator_free;
>  	it->key = decoded_key;
> -	it->iterator_type = type;
> +	it->is_eq = type == ITER_EQ || type == ITER_REQ;
> +	it->is_including = it->is_eq || type == ITER_GE || type == ITER_ALL ||
> +			   type == ITER_LE;
>  	it->format = index->format;
> -	it->module_id = iterator_type_is_reverse(type) ?
> -			SESSION_SETTING_MODULE_max - 1 : 0;
> -	it->current_id = iterator_type_is_reverse(type) ?
> -			 session_settings_modules[it->module_id].size - 1 : 0;
> +	if (!iterator_type_is_reverse(type)) {
> +		it->base.next = session_settings_iterator_next;
> +		it->module_id = 0;
> +		it->setting_id = 0;
> +	} else {
> +		it->base.next = session_settings_iterator_prev;
> +		it->module_id = session_setting_type_MAX - 1;
> +		struct session_setting_module *module =
> +			&session_setting_modules[it->module_id];
> +		it->setting_id = module->setting_count - 1;
> +	}
>  	return (struct iterator *)it;
>  }
>  
> @@ -192,23 +256,28 @@ session_settings_index_get(struct index *base, const char *key,
>  			   uint32_t part_count, struct tuple **result)
>  {
>  	struct session_settings_index *index =
> -		(struct session_settings_index *)base;
> +		(struct session_settings_index *) base;
>  	assert(part_count == 1);
> -	(void)part_count;
> +	(void) part_count;
>  	uint32_t len;
> -	const char *tmp = mp_decode_str(&key, &len);
> -	const char *decoded_key = tt_cstr(tmp, len);
> -	struct tuple *ret = NULL;
> -	struct tuple_format *format = index->format;
> -	for (int i = 0; i < SESSION_SETTING_MODULE_max; ++i) {
> -		if (session_settings_modules[i].get(format, 0, decoded_key,
> -						    ITER_EQ, NULL, &ret) != 0)
> -			return -1;
> -		if (ret != NULL)
> -			break;
> +	key = mp_decode_str(&key, &len);
> +	key = tt_cstr(key, len);
> +	struct session_setting_module *module = &session_setting_modules[0];
> +	struct session_setting_module *end = module + session_setting_type_MAX;
> +	int sid = 0;
> +	for (; module < end; ++module, sid = 0) {
> +		if (session_settings_next_in_module(module, &sid, key, true,
> +						    true) == 0)
> +			goto found;
>  	}
> -	*result = ret;
> +	*result = NULL;
>  	return 0;
> +found:;
> +	const char *mp_pair;
> +	const char *mp_pair_end;
> +	module->get(sid, &mp_pair, &mp_pair_end);
> +	*result = box_tuple_new(index->format, mp_pair, mp_pair_end);
> +	return *result != NULL ? 0 : -1;
>  }
>  
>  static const struct index_vtab session_settings_index_vtab = {
> @@ -281,70 +350,64 @@ session_settings_space_execute_update(struct space *space, struct txn *txn,
>  				      struct tuple **result)
>  {
>  	(void)txn;
> -	const char *data = request->key;
> -	uint32_t key_len = mp_decode_array(&data);
> +	struct tuple_format *format = space->format;
> +	const char *old_data, *old_data_end, *new_data;
> +	struct region *region = &fiber()->gc;
> +	size_t used = region_used(region);
> +	int rc = -1, sid = 0;
> +	struct index_def *pk_def = space->index[0]->def;
> +	uint64_t column_mask;
> +
> +	const char *new_key, *key = request->key;
> +	uint32_t new_size, new_key_len, key_len = mp_decode_array(&key);
>  	if (key_len == 0) {
>  		diag_set(ClientError, ER_EXACT_MATCH, 1, 0);
>  		return -1;
>  	}
> -	if (key_len > 1 || mp_typeof(*data) != MP_STR) {
> +	if (key_len > 1 || mp_typeof(*key) != MP_STR) {
>  		diag_set(ClientError, ER_KEY_PART_TYPE, 0, "string");
>  		return -1;
>  	}
> -	uint32_t len;
> -	const char *tmp = mp_decode_str(&data, &len);
> -	const char *decoded_key = tt_cstr(tmp, len);
> -
> -	int id;
> -	struct tuple *old_tuple = NULL;
> -	struct tuple_format *format = space->format;
> -	struct session_settings_modules *module = NULL;
> -	for (int i = 0; i < SESSION_SETTING_MODULE_max; ++i) {
> -		module = &session_settings_modules[i];
> -		if (module->get(format, 0, decoded_key, ITER_EQ, &id,
> -				&old_tuple) != 0)
> -			return -1;
> -		if (old_tuple != NULL)
> -			break;
> +	key = mp_decode_str(&key, &key_len);
> +	key = tt_cstr(key, key_len);
> +	struct session_setting_module *module = &session_setting_modules[0];
> +	struct session_setting_module *end = module + session_setting_type_MAX;
> +	for (; module < end; ++module, sid = 0) {
> +		if (session_settings_next_in_module(module, &sid, key, true,
> +						    true) == 0)
> +			goto found;
>  	}
> -	if (old_tuple == NULL)
> -		return 0;
> -
> -	uint32_t new_size = 0, bsize;
> -	const char *old_data = tuple_data_range(old_tuple, &bsize);
> -	const char *new_data =
> -		xrow_update_execute(request->tuple,request->tuple_end,
> -				    old_data, old_data + bsize, format->dict,
> -				    &new_size, request->index_base, NULL);
> +	*result = NULL;
> +	return 0;
> +found:
> +	module->get(sid, &old_data, &old_data_end);
> +	new_data = xrow_update_execute(request->tuple, request->tuple_end,
> +				       old_data, old_data_end, format->dict,
> +				       &new_size, request->index_base,
> +				       &column_mask);
>  	if (new_data == NULL)
> -		return -1;
> +		goto finish;
> +	*result = box_tuple_new(format, new_data, new_data + new_size);
> +	if (*result == NULL)
> +		goto finish;
>  
> -	uint32_t array_len = mp_decode_array(&new_data);
> -	if (array_len != 2)
> -		if (format->exact_field_count != array_len) {
> -		diag_set(ClientError, ER_EXACT_FIELD_COUNT,
> -			 array_len, format->exact_field_count);
> -		return -1;
> -	}
> -
> -	if (mp_typeof(*new_data) != MP_STR) {
> -		diag_set(ClientError, ER_CANT_UPDATE_PRIMARY_KEY,
> -			 space_index(space, 0)->def->name, space_name(space));
> -		return -1;
> -	}
> -
> -	const char *value = new_data;
> -	mp_next(&value);
> -	mp_decode_array(&old_data);
> -	uint32_t size_a = mp_sizeof_str(len);
> -	uint32_t size_b = value - new_data;
> -	if (size_a != size_b || memcmp(old_data, new_data, size_a) != 0) {
> -		diag_set(ClientError, ER_CANT_UPDATE_PRIMARY_KEY,
> -			 space_index(space, 0)->def->name, space_name(space));
> -		return -1;
> +	mp_decode_array(&new_data);
> +	new_key = mp_decode_str(&new_data, &new_key_len);
> +	if (!key_update_can_be_skipped(pk_def->key_def->column_mask,
> +				       column_mask)) {
> +		if (key_len != new_key_len ||
> +		    memcmp(key, new_key, key_len) != 0) {
> +			diag_set(ClientError, ER_CANT_UPDATE_PRIMARY_KEY,
> +				 pk_def->name, space_name(space));
> +			goto finish;
> +		}
>  	}
> -
> -	return module->set(format, id, value, result);
> +	if (module->set(sid, new_data) != 0)
> +		goto finish;
> +	rc = 0;
> +finish:
> +	region_truncate(region, used);
> +	return rc;
>  }
>  
>  static int
> diff --git a/src/box/session_settings.h b/src/box/session_settings.h
> index e7ecbdb08..7415e0eea 100644
> --- a/src/box/session_settings.h
> +++ b/src/box/session_settings.h
> @@ -29,33 +29,41 @@
>   * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
>   * SUCH DAMAGE.
>   */
> -#include <stddef.h>
> -#include <stdint.h>
> -#include "iterator_type.h"
>  
> -#if defined(__cplusplus)
> -extern "C" {
> -#endif /* defined(__cplusplus) */
> -
> -enum session_setting_module {
> -	SESSION_SETTING_MODULE_max
> +/**
> + * Session has settings. Settings belong to different subsystems,
> + * such as SQL. Each subsystem registers here its session setting
> + * type and a set of settings with getter and setter functions.
> + * The self-registration of modules allows session setting code
> + * not to depend on all the subsystems.
> + *
> + * The types should be ordered in alphabetical order, because the
> + * type list is used by setting iterators.
> + */
> +enum session_setting_type {
> +	session_setting_type_MAX,
>  };
>  
> -struct tuple;
> -struct tuple_format;
> -
> -struct session_settings_modules {
> -	enum session_setting_module id;
> -	uint32_t size;
> -	int (*get)(struct tuple_format *format, int id, const char *key,
> -		   enum iterator_type type, int *end_id, struct tuple **result);
> -	int (*set)(struct tuple_format *format, int id, const char *value,
> -		   struct tuple **result);
> +struct session_setting_module {
> +	/**
> +	 * An array of setting names. All of them should have the
> +	 * same prefix.
> +	 */
> +	const char **settings;
> +	/** Count of settings. */
> +	int setting_count;
> +	/**
> +	 * Get a MessagePack encoded pair [name, value] for a
> +	 * setting having index @a id. Index is from the settings
> +	 * array.
> +	 */
> +	void (*get)(int id, const char **mp_pair, const char **mp_pair_end);
> +	/**
> +	 * Set value of a setting by a given @a id from a
> +	 * MessagePack encoded buffer. Note, it is not a pair, but
> +	 * just value.
> +	 */
> +	int (*set)(int id, const char *mp_value);
>  };
>  
> -extern struct session_settings_modules session_settings_modules[];
> -extern const struct space_vtab session_settings_space_vtab;
> -
> -#if defined(__cplusplus)
> -} /* extern "C" */
> -#endif /* defined(__plusplus) */
> +extern struct session_setting_module session_setting_modules[];
> diff --git a/src/box/virtual_engine.c b/src/box/virtual_engine.c
> index 5ec8015c7..36eec8323 100644
> --- a/src/box/virtual_engine.c
> +++ b/src/box/virtual_engine.c
> @@ -28,10 +28,12 @@
>   * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
>   * SUCH DAMAGE.
>   */
> -#include "session_settings.h"
>  #include "virtual_engine.h"
> +#include "tuple.h"
>  #include "schema.h"
>  
> +extern const struct space_vtab session_settings_space_vtab;
> +
>  static void
>  virtual_engine_shutdown(struct engine *engine)
>  {
> 


>From 7efcc8500ae9c58ad12ee7eadc8833be70a590c1 Mon Sep 17 00:00:00 2001
From: Mergen Imeev <imeevma at gmail.com>
Date: Sat, 30 Nov 2019 12:59:45 +0300
Subject: [PATCH] box: introduce _session_settings system space

This patch creates _session_settings system space. This space is
used to view and change session settings. There are no settings at
the moment, some will be added in the next patch.

Part of #4511

diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index d79d52c..b57e90b 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -80,6 +80,7 @@ add_library(box STATIC
     sysview.c
     blackhole.c
     virtual_engine.c
+    session_settings.c
     vinyl.c
     vy_stmt.c
     vy_mem.c
diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap
index 7ff3aa5..c573eec 100644
Binary files a/src/box/bootstrap.snap and b/src/box/bootstrap.snap differ
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index f6e96f0..01b58af 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -653,6 +653,8 @@ box_lua_space_init(struct lua_State *L)
 	lua_setfield(L, -2, "SPACE_SEQUENCE_ID");
 	lua_pushnumber(L, BOX_FUNC_INDEX_ID);
 	lua_setfield(L, -2, "FUNC_INDEX_ID");
+	lua_pushnumber(L, BOX_SESSION_SETTINGS_ID);
+	lua_setfield(L, -2, "SESSION_SETTINGS_ID");
 	lua_pushnumber(L, BOX_SYSTEM_ID_MIN);
 	lua_setfield(L, -2, "SYSTEM_ID_MIN");
 	lua_pushnumber(L, BOX_SYSTEM_ID_MAX);
diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua
index 07f1e03..4dfd571 100644
--- a/src/box/lua/upgrade.lua
+++ b/src/box/lua/upgrade.lua
@@ -951,8 +951,23 @@ local function drop_func_collation()
     _func.index.name:alter({parts = {{'name', 'string'}}})
 end
 
+local function create_session_settings_space()
+    local _space = box.space[box.schema.SPACE_ID]
+    local _index = box.space[box.schema.INDEX_ID]
+    local format = {}
+    format[1] = {name='name', type='string'}
+    format[2] = {name='value', type='any'}
+    log.info("create space _session_settings")
+    _space:insert{box.schema.SESSION_SETTINGS_ID, ADMIN, '_session_settings',
+                  'virtual', 2, {temporary = true}, format}
+    log.info("create index _session_settings:primary")
+    _index:insert{box.schema.SESSION_SETTINGS_ID, 0, 'primary', 'tree',
+                  {unique = true}, {{0, 'string'}}}
+end
+
 local function upgrade_to_2_3_1()
     drop_func_collation()
+    create_session_settings_space()
 end
 
 --------------------------------------------------------------------------------
diff --git a/src/box/schema_def.h b/src/box/schema_def.h
index ba870ff..f86cd42 100644
--- a/src/box/schema_def.h
+++ b/src/box/schema_def.h
@@ -114,6 +114,8 @@ enum {
 	BOX_CK_CONSTRAINT_ID = 364,
 	/** Space id of _func_index. */
 	BOX_FUNC_INDEX_ID = 372,
+	/** Space id of _session_settings. */
+	BOX_SESSION_SETTINGS_ID = 380,
 	/** End of the reserved range of system spaces. */
 	BOX_SYSTEM_ID_MAX = 511,
 	BOX_ID_NIL = 2147483647
@@ -277,6 +279,12 @@ enum {
 	BOX_FUNC_INDEX_FUNCTION_ID = 2,
 };
 
+/** _session_settings fields. */
+enum {
+	BOX_SESSION_SETTINGS_FIELD_NAME = 0,
+	BOX_SESSION_SETTINGS_FIELD_VALUE = 1,
+};
+
 /*
  * Different objects which can be subject to access
  * control.
diff --git a/src/box/session_settings.c b/src/box/session_settings.c
new file mode 100644
index 0000000..dd1874b
--- /dev/null
+++ b/src/box/session_settings.c
@@ -0,0 +1,472 @@
+/*
+ * 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 <COPYRIGHT HOLDER> ``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
+ * <COPYRIGHT HOLDER> 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 "session_settings.h"
+#include "xrow_update.h"
+#include "virtual_engine.h"
+#include "column_mask.h"
+#include "session.h"
+#include "schema.h"
+#include "tuple.h"
+#include "xrow.h"
+#include "sql.h"
+
+struct session_setting_module
+	session_setting_modules[session_setting_type_MAX] = {};
+
+struct session_settings_index {
+	/** Base index. Must be the first member. */
+	struct index base;
+	/**
+	 * Format of the tuples iterators of this index return. It
+	 * is stored here so as not to lookup space each time to
+	 * get a format and create an iterator.
+	 */
+	struct tuple_format *format;
+};
+
+struct session_settings_iterator {
+	/** Base iterator. Must be the first member. */
+	struct iterator base;
+	/**
+	 * Format of the tuples this iterator returns. It is
+	 * stored here so as not to lookup space each time to get
+	 * a format for selected tuples.
+	 */
+	struct tuple_format *format;
+	/**
+	 * ID of the current session settings module in the global
+	 * list of the modules.
+	 */
+	int module_id;
+	/** ID of the setting in current module. */
+	int setting_id;
+	/** Decoded key. */
+	char *key;
+	/** True if the iterator returns only equal keys. */
+	bool is_eq;
+	/** True if the iterator should include equal keys. */
+	bool is_including;
+};
+
+static void
+session_settings_iterator_free(struct iterator *ptr)
+{
+	struct session_settings_iterator *it =
+		(struct session_settings_iterator *)ptr;
+	free(it->key);
+	free(it);
+}
+
+static int
+session_settings_next_in_module(const struct session_setting_module *module,
+				int *sid, const char *key, bool is_eq,
+				bool is_including)
+{
+	int i = *sid;
+	int count = module->setting_count;
+	if (i >= count)
+		return -1;
+	if (key == NULL)
+		return 0;
+	assert(i >= 0);
+	const char **name = &module->settings[i];
+	for (; i < count; ++i, ++name) {
+		int cmp = strcmp(*name, key);
+		if ((cmp == 0 && is_including) ||
+		    (cmp > 0 && !is_eq)) {
+			*sid = i;
+			return 0;
+		}
+	}
+	*sid = count;
+	return -1;
+}
+
+static int
+session_settings_prev_in_module(const struct session_setting_module *module,
+				int *sid, const char *key, bool is_eq,
+				bool is_including)
+{
+	int i = *sid;
+	int count = module->setting_count;
+	if (i < 0)
+		return -1;
+	if (key == NULL)
+		return 0;
+	if (i >= count)
+		i = count - 1;
+	const char **name = &module->settings[i];
+	for (; i >= 0; --i, --name) {
+		int cmp = strcmp(*name, key);
+		if ((cmp == 0 && is_including) ||
+		    (cmp < 0 && !is_eq)) {
+			*sid = i;
+			return 0;
+		}
+	}
+	*sid = -1;
+	return -1;
+}
+
+static int
+session_settings_iterator_next(struct iterator *iterator, struct tuple **result)
+{
+	struct session_settings_iterator *it =
+		(struct session_settings_iterator *)iterator;
+	int mid = it->module_id, sid = it->setting_id;
+	struct session_setting_module *module;
+	const char *key = it->key;
+	bool is_including = it->is_including, is_eq = it->is_eq;
+	bool is_found = false;
+	for (; mid < session_setting_type_MAX; ++mid, sid = 0) {
+		module = &session_setting_modules[mid];
+		if (session_settings_next_in_module(module, &sid, key, is_eq,
+						    is_including) == 0) {
+			is_found = true;
+			break;
+		}
+	}
+	it->module_id = mid;
+	it->setting_id = sid + 1;
+	if (!is_found) {
+		*result = NULL;
+		return 0;
+	}
+	const char *mp_pair, *mp_pair_end;
+	module->get(sid, &mp_pair, &mp_pair_end);
+	*result = box_tuple_new(it->format, mp_pair, mp_pair_end);
+	return *result != NULL ? 0 : -1;
+}
+
+static int
+session_settings_iterator_prev(struct iterator *iterator, struct tuple **result)
+{
+	struct session_settings_iterator *it =
+		(struct session_settings_iterator *)iterator;
+	int mid = it->module_id, sid = it->setting_id;
+	struct session_setting_module *module;
+	const char *key = it->key;
+	bool is_including = it->is_including, is_eq = it->is_eq;
+	bool is_found = false;
+	for (; mid >= 0; --mid, sid = INT_MAX) {
+		module = &session_setting_modules[mid];
+		if (session_settings_prev_in_module(module, &sid, key, is_eq,
+						    is_including) == 0) {
+			is_found = true;
+			break;
+		}
+	}
+	it->module_id = mid;
+	it->setting_id = sid - 1;
+	if (!is_found) {
+		*result = NULL;
+		return 0;
+	}
+	const char *mp_pair, *mp_pair_end;
+	module->get(sid, &mp_pair, &mp_pair_end);
+	*result = box_tuple_new(it->format, mp_pair, mp_pair_end);
+	return *result != NULL ? 0 : -1;
+}
+
+static void
+session_settings_index_destroy(struct index *index)
+{
+	free(index);
+}
+
+static struct iterator *
+session_settings_index_create_iterator(struct index *base,
+				       enum iterator_type type, const char *key,
+				       uint32_t part_count)
+{
+	struct session_settings_index *index =
+		(struct session_settings_index *)base;
+	char *decoded_key = NULL;
+	if (part_count > 0) {
+		assert(part_count == 1);
+		assert(mp_typeof(*key) == MP_STR);
+		uint32_t len;
+		const char *name = mp_decode_str(&key, &len);
+		decoded_key = (char *)malloc(len + 1);
+		if (decoded_key == NULL) {
+			diag_set(OutOfMemory, len + 1, "malloc", "decoded_key");
+			return NULL;
+		}
+		memcpy(decoded_key, name, len);
+		decoded_key[len] = '\0';
+	}
+	struct session_settings_iterator *it =
+		(struct session_settings_iterator *)malloc(sizeof(*it));
+	if (it == NULL) {
+		diag_set(OutOfMemory, sizeof(*it), "malloc", "it");
+		free(decoded_key);
+		return NULL;
+	}
+	iterator_create(&it->base, base);
+	it->base.free = session_settings_iterator_free;
+	it->key = decoded_key;
+	it->is_eq = type == ITER_EQ || type == ITER_REQ;
+	it->is_including = it->is_eq || type == ITER_GE || type == ITER_ALL ||
+			   type == ITER_LE;
+	it->format = index->format;
+	if (!iterator_type_is_reverse(type)) {
+		it->base.next = session_settings_iterator_next;
+		it->module_id = 0;
+		it->setting_id = 0;
+	} else {
+		it->base.next = session_settings_iterator_prev;
+		it->module_id = session_setting_type_MAX - 1;
+		struct session_setting_module *module =
+			&session_setting_modules[it->module_id];
+		it->setting_id = module->setting_count - 1;
+	}
+	return (struct iterator *)it;
+}
+
+static int
+session_settings_index_get(struct index *base, const char *key,
+			   uint32_t part_count, struct tuple **result)
+{
+	struct session_settings_index *index =
+		(struct session_settings_index *) base;
+	assert(part_count == 1);
+	(void) part_count;
+	uint32_t len;
+	key = mp_decode_str(&key, &len);
+	key = tt_cstr(key, len);
+	struct session_setting_module *module = &session_setting_modules[0];
+	struct session_setting_module *end = module + session_setting_type_MAX;
+	int sid = 0;
+	for (; module < end; ++module, sid = 0) {
+		if (session_settings_next_in_module(module, &sid, key, true,
+						    true) == 0)
+			goto found;
+	}
+	*result = NULL;
+	return 0;
+found:;
+	const char *mp_pair;
+	const char *mp_pair_end;
+	module->get(sid, &mp_pair, &mp_pair_end);
+	*result = box_tuple_new(index->format, mp_pair, mp_pair_end);
+	return *result != NULL ? 0 : -1;
+}
+
+static const struct index_vtab session_settings_index_vtab = {
+	/* .destroy = */ session_settings_index_destroy,
+	/* .commit_create = */ generic_index_commit_create,
+	/* .abort_create = */ generic_index_abort_create,
+	/* .commit_modify = */ generic_index_commit_modify,
+	/* .commit_drop = */ generic_index_commit_drop,
+	/* .update_def = */ generic_index_update_def,
+	/* .depends_on_pk = */ generic_index_depends_on_pk,
+	/* .def_change_requires_rebuild = */
+		generic_index_def_change_requires_rebuild,
+	/* .size = */ generic_index_size,
+	/* .bsize = */ generic_index_bsize,
+	/* .min = */ generic_index_min,
+	/* .max = */ generic_index_max,
+	/* .random = */ generic_index_random,
+	/* .count = */ generic_index_count,
+	/* .get = */ session_settings_index_get,
+	/* .replace = */ generic_index_replace,
+	/* .create_iterator = */ session_settings_index_create_iterator,
+	/* .create_snapshot_iterator = */
+		generic_index_create_snapshot_iterator,
+	/* .stat = */ generic_index_stat,
+	/* .compact = */ generic_index_compact,
+	/* .reset_stat = */ generic_index_reset_stat,
+	/* .begin_build = */ generic_index_begin_build,
+	/* .reserve = */ generic_index_reserve,
+	/* .build_next = */ generic_index_build_next,
+	/* .end_build = */ generic_index_end_build,
+};
+
+static void
+session_settings_space_destroy(struct space *space)
+{
+	free(space);
+}
+
+static int
+session_settings_space_execute_replace(struct space *space, struct txn *txn,
+				       struct request *request,
+				       struct tuple **result)
+{
+	(void)space;
+	(void)txn;
+	(void)request;
+	(void)result;
+	diag_set(ClientError, ER_UNSUPPORTED, "Session_settings space",
+		 "replace()");
+	return -1;
+}
+
+static int
+session_settings_space_execute_delete(struct space *space, struct txn *txn,
+				      struct request *request,
+				      struct tuple **result)
+{
+	(void)space;
+	(void)txn;
+	(void)request;
+	(void)result;
+	diag_set(ClientError, ER_UNSUPPORTED, "Session_settings space",
+		 "delete()");
+	return -1;
+}
+
+static int
+session_settings_space_execute_update(struct space *space, struct txn *txn,
+				      struct request *request,
+				      struct tuple **result)
+{
+	(void)txn;
+	struct tuple_format *format = space->format;
+	const char *old_data, *old_data_end, *new_data;
+	struct region *region = &fiber()->gc;
+	size_t used = region_used(region);
+	int rc = -1, sid = 0;
+	struct index_def *pk_def = space->index[0]->def;
+	uint64_t column_mask;
+
+	const char *new_key, *key = request->key;
+	uint32_t new_size, new_key_len, key_len = mp_decode_array(&key);
+	if (key_len == 0) {
+		diag_set(ClientError, ER_EXACT_MATCH, 1, 0);
+		return -1;
+	}
+	if (key_len > 1 || mp_typeof(*key) != MP_STR) {
+		diag_set(ClientError, ER_KEY_PART_TYPE, 0, "string");
+		return -1;
+	}
+	key = mp_decode_str(&key, &key_len);
+	key = tt_cstr(key, key_len);
+	struct session_setting_module *module = &session_setting_modules[0];
+	struct session_setting_module *end = module + session_setting_type_MAX;
+	for (; module < end; ++module, sid = 0) {
+		if (session_settings_next_in_module(module, &sid, key, true,
+						    true) == 0)
+			goto found;
+	}
+	*result = NULL;
+	return 0;
+found:
+	module->get(sid, &old_data, &old_data_end);
+	new_data = xrow_update_execute(request->tuple, request->tuple_end,
+				       old_data, old_data_end, format->dict,
+				       &new_size, request->index_base,
+				       &column_mask);
+	if (new_data == NULL)
+		goto finish;
+	*result = box_tuple_new(format, new_data, new_data + new_size);
+	if (*result == NULL)
+		goto finish;
+
+	mp_decode_array(&new_data);
+	new_key = mp_decode_str(&new_data, &new_key_len);
+	if (!key_update_can_be_skipped(pk_def->key_def->column_mask,
+				       column_mask)) {
+		if (key_len != new_key_len ||
+		    memcmp(key, new_key, key_len) != 0) {
+			diag_set(ClientError, ER_CANT_UPDATE_PRIMARY_KEY,
+				 pk_def->name, space_name(space));
+			goto finish;
+		}
+	}
+	if (module->set(sid, new_data) != 0)
+		goto finish;
+	rc = 0;
+finish:
+	region_truncate(region, used);
+	return rc;
+}
+
+static int
+session_settings_space_execute_upsert(struct space *space, struct txn *txn,
+				      struct request *request)
+{
+	(void)space;
+	(void)txn;
+	(void)request;
+	diag_set(ClientError, ER_UNSUPPORTED, "Session_settings space",
+		 "upsert()");
+	return -1;
+}
+
+static struct index *
+session_settings_space_create_index(struct space *space, struct index_def *def)
+{
+	assert(space->def->id == BOX_SESSION_SETTINGS_ID);
+	if (def->iid != 0) {
+		diag_set(ClientError, ER_UNSUPPORTED, "Session_settings space",
+			 "create_index()");
+		return NULL;
+	}
+
+	struct session_settings_index *index =
+		(struct session_settings_index *)calloc(1, sizeof(*index));
+	if (index == NULL) {
+		diag_set(OutOfMemory, sizeof(*index), "calloc", "index");
+		return NULL;
+	}
+	if (index_create(&index->base, space->engine,
+			 &session_settings_index_vtab, def) != 0) {
+		free(index);
+		return NULL;
+	}
+
+	index->format = space->format;
+	return &index->base;
+}
+
+const struct space_vtab session_settings_space_vtab = {
+	/* .destroy = */ session_settings_space_destroy,
+	/* .bsize = */ generic_space_bsize,
+	/* .execute_replace = */ session_settings_space_execute_replace,
+	/* .execute_delete = */ session_settings_space_execute_delete,
+	/* .execute_update = */ session_settings_space_execute_update,
+	/* .execute_upsert = */ session_settings_space_execute_upsert,
+	/* .ephemeral_replace = */ generic_space_ephemeral_replace,
+	/* .ephemeral_delete = */ generic_space_ephemeral_delete,
+	/* .ephemeral_rowid_next = */ generic_space_ephemeral_rowid_next,
+	/* .init_system_space = */ generic_init_system_space,
+	/* .init_ephemeral_space = */ generic_init_ephemeral_space,
+	/* .check_index_def = */ generic_space_check_index_def,
+	/* .create_index = */ session_settings_space_create_index,
+	/* .add_primary_key = */ generic_space_add_primary_key,
+	/* .drop_primary_key = */ generic_space_drop_primary_key,
+	/* .check_format = */ generic_space_check_format,
+	/* .build_index = */ generic_space_build_index,
+	/* .swap_index = */ generic_space_swap_index,
+	/* .prepare_alter = */ generic_space_prepare_alter,
+	/* .invalidate = */ generic_space_invalidate,
+};
diff --git a/src/box/session_settings.h b/src/box/session_settings.h
new file mode 100644
index 0000000..7415e0e
--- /dev/null
+++ b/src/box/session_settings.h
@@ -0,0 +1,69 @@
+#pragma once
+/*
+ * 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 <COPYRIGHT HOLDER> ``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
+ * <COPYRIGHT HOLDER> 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.
+ */
+
+/**
+ * Session has settings. Settings belong to different subsystems,
+ * such as SQL. Each subsystem registers here its session setting
+ * type and a set of settings with getter and setter functions.
+ * The self-registration of modules allows session setting code
+ * not to depend on all the subsystems.
+ *
+ * The types should be ordered in alphabetical order, because the
+ * type list is used by setting iterators.
+ */
+enum session_setting_type {
+	session_setting_type_MAX,
+};
+
+struct session_setting_module {
+	/**
+	 * An array of setting names. All of them should have the
+	 * same prefix.
+	 */
+	const char **settings;
+	/** Count of settings. */
+	int setting_count;
+	/**
+	 * Get a MessagePack encoded pair [name, value] for a
+	 * setting having index @a id. Index is from the settings
+	 * array.
+	 */
+	void (*get)(int id, const char **mp_pair, const char **mp_pair_end);
+	/**
+	 * Set value of a setting by a given @a id from a
+	 * MessagePack encoded buffer. Note, it is not a pair, but
+	 * just value.
+	 */
+	int (*set)(int id, const char *mp_value);
+};
+
+extern struct session_setting_module session_setting_modules[];
diff --git a/src/box/virtual_engine.c b/src/box/virtual_engine.c
index 9a59a3f..36eec83 100644
--- a/src/box/virtual_engine.c
+++ b/src/box/virtual_engine.c
@@ -29,8 +29,11 @@
  * SUCH DAMAGE.
  */
 #include "virtual_engine.h"
+#include "tuple.h"
 #include "schema.h"
 
+extern const struct space_vtab session_settings_space_vtab;
+
 static void
 virtual_engine_shutdown(struct engine *engine)
 {
@@ -41,13 +44,50 @@ static struct space *
 virtual_engine_create_space(struct engine *engine, struct space_def *def,
 			    struct rlist *key_list)
 {
-	(void)engine;
-	(void)def;
-	(void)key_list;
-	/* There are currently no spaces with this engine. */
-	diag_set(ClientError, ER_UNSUPPORTED, "Tarantool",
-		 "spaces with this engine.");
-	return NULL;
+	/*
+	 * At the moment the only space that have this engine is
+	 * _session_sessings.
+	 */
+	if (def->id != BOX_SESSION_SETTINGS_ID) {
+		diag_set(ClientError, ER_UNSUPPORTED, "Tarantool",
+			 "non-system space with this engine.");
+		return NULL;
+	}
+	const struct space_vtab *space_vtab = &session_settings_space_vtab;
+
+	struct space *space = (struct space *)calloc(1, sizeof(*space));
+	if (space == NULL) {
+		diag_set(OutOfMemory, sizeof(*space), "calloc", "space");
+		return NULL;
+	}
+	int key_count = 0;
+	struct key_def **keys = index_def_to_key_def(key_list, &key_count);
+	if (keys == NULL) {
+		free(space);
+		return NULL;
+	}
+	struct tuple_format *format =
+		tuple_format_new(&tuple_format_runtime->vtab, NULL, keys,
+				 key_count, def->fields, def->field_count,
+				 def->exact_field_count, def->dict,
+				 def->opts.is_temporary,
+				 def->opts.is_ephemeral);
+	if (format == NULL) {
+		free(space);
+		return NULL;
+	}
+	tuple_format_ref(format);
+	int rc = space_create(space, engine, space_vtab, def, key_list, format);
+	/*
+	 * Format is now referenced by the space if space has beed
+	 * created.
+	 */
+	tuple_format_unref(format);
+	if (rc != 0) {
+		free(space);
+		return NULL;
+	}
+	return space;
 }
 
 static const struct engine_vtab virtual_engine_vtab = {
diff --git a/test/app-tap/tarantoolctl.test.lua b/test/app-tap/tarantoolctl.test.lua
index 7a07860..4d70595 100755
--- a/test/app-tap/tarantoolctl.test.lua
+++ b/test/app-tap/tarantoolctl.test.lua
@@ -415,8 +415,8 @@ do
             check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 1", "\n", 3)
             check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 1 --replica 2", "\n", 3)
             check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 2", "\n", 0)
-            check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 24)
-            check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 52)
+            check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 25)
+            check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 53)
         end)
     end)
 
diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result
index 938a763..f2ad75e 100644
--- a/test/box-py/bootstrap.result
+++ b/test/box-py/bootstrap.result
@@ -96,6 +96,8 @@ box.space._space:select{}
         'type': 'boolean'}]]
   - [372, 1, '_func_index', 'memtx', 0, {}, [{'name': 'space_id', 'type': 'unsigned'},
       {'name': 'index_id', 'type': 'unsigned'}, {'name': 'func_id', 'type': 'unsigned'}]]
+  - [380, 1, '_session_settings', 'virtual', 2, {'temporary': true}, [{'name': 'name',
+        'type': 'string'}, {'name': 'value', 'type': 'any'}]]
 ...
 box.space._index:select{}
 ---
@@ -153,6 +155,7 @@ box.space._index:select{}
   - [364, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned'], [1, 'string']]]
   - [372, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned'], [1, 'unsigned']]]
   - [372, 1, 'fid', 'tree', {'unique': false}, [[2, 'unsigned']]]
+  - [380, 0, 'primary', 'tree', {'unique': true}, [[0, 'string']]]
 ...
 box.space._user:select{}
 ---
diff --git a/test/box/access_sysview.result b/test/box/access_sysview.result
index 1f33dec..799d19f 100644
--- a/test/box/access_sysview.result
+++ b/test/box/access_sysview.result
@@ -246,11 +246,11 @@ box.session.su('guest')
 ...
 #box.space._vspace:select{}
 ---
-- 25
+- 26
 ...
 #box.space._vindex:select{}
 ---
-- 53
+- 54
 ...
 #box.space._vuser:select{}
 ---
@@ -282,7 +282,7 @@ box.session.su('guest')
 ...
 #box.space._vindex:select{}
 ---
-- 53
+- 54
 ...
 #box.space._vuser:select{}
 ---
diff --git a/test/box/alter.result b/test/box/alter.result
index 9a2f991..f150faa 100644
--- a/test/box/alter.result
+++ b/test/box/alter.result
@@ -92,7 +92,7 @@ space = box.space[t[1]]
 ...
 space.id
 ---
-- 373
+- 381
 ...
 space.field_count
 ---
@@ -137,7 +137,7 @@ space_deleted
 ...
 space:replace{0}
 ---
-- error: Space '373' does not exist
+- error: Space '381' does not exist
 ...
 _index:insert{_space.id, 0, 'primary', 'tree', {unique=true}, {{0, 'unsigned'}}}
 ---
@@ -220,6 +220,7 @@ _index:select{}
   - [364, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned'], [1, 'string']]]
   - [372, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned'], [1, 'unsigned']]]
   - [372, 1, 'fid', 'tree', {'unique': false}, [[2, 'unsigned']]]
+  - [380, 0, 'primary', 'tree', {'unique': true}, [[0, 'string']]]
 ...
 -- modify indexes of a system space
 _index:delete{_index.id, 0}
diff --git a/test/box/gh-4511-access-settings-from-any-frontend.result b/test/box/gh-4511-access-settings-from-any-frontend.result
index 9874616..75d53cf 100644
--- a/test/box/gh-4511-access-settings-from-any-frontend.result
+++ b/test/box/gh-4511-access-settings-from-any-frontend.result
@@ -6,5 +6,111 @@ test_run = require('test_run').new()
 -- User cannot create spaces with this engine.
 s = box.schema.space.create('test', {engine = 'virtual'})
  | ---
- | - error: Tarantool does not support spaces with this engine.
+ | - error: Tarantool does not support non-system space with this engine.
+ | ...
+
+-- Check _session_settings space.
+s = box.space._session_settings
+ | ---
+ | ...
+s:format()
+ | ---
+ | - [{'name': 'name', 'type': 'string'}, {'name': 'value', 'type': 'any'}]
+ | ...
+
+-- Make sure that we cannot drop space.
+s:drop()
+ | ---
+ | - error: Can't drop the primary key in a system space, space '_session_settings'
+ | ...
+
+--
+-- Make sure, that session_settings space doesn't support
+-- create_index(), insert(), replace() and delete() methods.
+--
+s:create_index('a')
+ | ---
+ | - error: Session_settings space does not support create_index()
+ | ...
+s:insert({'a', 1})
+ | ---
+ | - error: Session_settings space does not support replace()
+ | ...
+s:delete({'b'})
+ | ---
+ | - error: Session_settings space does not support delete()
+ | ...
+s:replace({'sql_defer_foreign_keys', true})
+ | ---
+ | - error: Session_settings space does not support replace()
+ | ...
+
+-- Check get() and select(). They should return nothing for now.
+s:get({'a'})
+ | ---
+ | ...
+s:select()
+ | ---
+ | - []
+ | ...
+s:select({}, {iterator='EQ'})
+ | ---
+ | - []
+ | ...
+s:select({}, {iterator='ALL'})
+ | ---
+ | - []
+ | ...
+s:select({}, {iterator='GE'})
+ | ---
+ | - []
+ | ...
+s:select({}, {iterator='GT'})
+ | ---
+ | - []
+ | ...
+s:select({}, {iterator='REQ'})
+ | ---
+ | - []
+ | ...
+s:select({}, {iterator='LE'})
+ | ---
+ | - []
+ | ...
+s:select({}, {iterator='LT'})
+ | ---
+ | - []
+ | ...
+s:select({'a'}, {iterator='EQ'})
+ | ---
+ | - []
+ | ...
+s:select({'a'}, {iterator='ALL'})
+ | ---
+ | - []
+ | ...
+s:select({'a'}, {iterator='GE'})
+ | ---
+ | - []
+ | ...
+s:select({'a'}, {iterator='GT'})
+ | ---
+ | - []
+ | ...
+s:select({'a'}, {iterator='REQ'})
+ | ---
+ | - []
+ | ...
+s:select({'a'}, {iterator='LE'})
+ | ---
+ | - []
+ | ...
+s:select({'a'}, {iterator='LT'})
+ | ---
+ | - []
+ | ...
+
+-- Currently there is nothing to update, but update() should work.
+s:update('some_option', {{'=', 'value', true}})
+ | ---
  | ...
diff --git a/test/box/gh-4511-access-settings-from-any-frontend.test.lua b/test/box/gh-4511-access-settings-from-any-frontend.test.lua
index 611caef..3304454 100644
--- a/test/box/gh-4511-access-settings-from-any-frontend.test.lua
+++ b/test/box/gh-4511-access-settings-from-any-frontend.test.lua
@@ -2,3 +2,40 @@ test_run = require('test_run').new()
 
 -- User cannot create spaces with this engine.
 s = box.schema.space.create('test', {engine = 'virtual'})
+
+-- Check _session_settings space.
+s = box.space._session_settings
+s:format()
+
+-- Make sure that we cannot drop space.
+s:drop()
+
+--
+-- Make sure, that session_settings space doesn't support
+-- create_index(), insert(), replace() and delete() methods.
+--
+s:create_index('a')
+s:insert({'a', 1})
+s:delete({'b'})
+s:replace({'sql_defer_foreign_keys', true})
+
+-- Check get() and select(). They should return nothing for now.
+s:get({'a'})
+s:select()
+s:select({}, {iterator='EQ'})
+s:select({}, {iterator='ALL'})
+s:select({}, {iterator='GE'})
+s:select({}, {iterator='GT'})
+s:select({}, {iterator='REQ'})
+s:select({}, {iterator='LE'})
+s:select({}, {iterator='LT'})
+s:select({'a'}, {iterator='EQ'})
+s:select({'a'}, {iterator='ALL'})
+s:select({'a'}, {iterator='GE'})
+s:select({'a'}, {iterator='GT'})
+s:select({'a'}, {iterator='REQ'})
+s:select({'a'}, {iterator='LE'})
+s:select({'a'}, {iterator='LT'})
+
+-- Currently there is nothing to update, but update() should work.
+s:update('some_option', {{'=', 'value', true}})
diff --git a/test/wal_off/alter.result b/test/wal_off/alter.result
index 62cb11d..97f7e6f 100644
--- a/test/wal_off/alter.result
+++ b/test/wal_off/alter.result
@@ -28,7 +28,7 @@ end;
 ...
 #spaces;
 ---
-- 65502
+- 65501
 ...
 -- cleanup
 for k, v in pairs(spaces) do


More information about the Tarantool-patches mailing list