From f688ef36a8fe14857099a254ba5c21cf93928cc5 Mon Sep 17 00:00:00 2001
From: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>
Date: Thu, 2 Nov 2017 00:35:05 +0300
Subject: [PATCH] schema: allow to store custom fields in format's field
 definition

Some users store in format fields their custom keys. But current
opts parser does not allow to store any unknown keys. Lets allow it.

Example:
format = {}
format[1] = {name = 'field1', type = 'unsigned', custom_field = 'custom_value'}
s = box.schema.create_space('test', {format = format})
s:format()[1].custom_field == 'custom_value'

Closes #2839
---
 src/box/alter.cc      |  5 +++--
 src/box/opt_def.c     | 17 +++++++++++------
 src/box/opt_def.h     | 17 +++++++++++++++--
 test/box/ddl.result   | 25 +++++++++++++++++++++++++
 test/box/ddl.test.lua | 11 +++++++++++
 5 files changed, 65 insertions(+), 10 deletions(-)

diff --git a/src/box/alter.cc b/src/box/alter.cc
index 324b7dc2c5..9215c07fd5 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -372,8 +372,9 @@ field_def_decode(struct field_def *field, const char **data,
 		uint32_t key_len;
 		const char *key = mp_decode_str(data, &key_len);
 		if (opts_parse_key(field, field_def_reg, key, key_len, data,
-			       ER_WRONG_SPACE_FORMAT,
-			       fieldno + TUPLE_INDEX_BASE, region) != 0)
+				   ER_WRONG_SPACE_FORMAT,
+				   fieldno + TUPLE_INDEX_BASE, region,
+				   true) != 0)
 			diag_raise();
 	}
 	if (field->name == NULL) {
diff --git a/src/box/opt_def.c b/src/box/opt_def.c
index ebb219c83e..cd93c23b81 100644
--- a/src/box/opt_def.c
+++ b/src/box/opt_def.c
@@ -144,7 +144,8 @@ opt_set(void *opts, const struct opt_def *def, const char **val,
 int
 opts_parse_key(void *opts, const struct opt_def *reg, const char *key,
 	       uint32_t key_len, const char **data, uint32_t errcode,
-	       uint32_t field_no, struct region *region)
+	       uint32_t field_no, struct region *region,
+	       bool skip_unknown_options)
 {
 	char errmsg[DIAG_ERRMSG_MAX];
 	bool found = false;
@@ -163,10 +164,14 @@ opts_parse_key(void *opts, const struct opt_def *reg, const char *key,
 		break;
 	}
 	if (!found) {
-		snprintf(errmsg, sizeof(errmsg), "unexpected option '%.*s'",
-			 key_len, key);
-		diag_set(ClientError, errcode, field_no, errmsg);
-		return -1;
+		if (skip_unknown_options) {
+			mp_next(data);
+		} else {
+			snprintf(errmsg, sizeof(errmsg),
+				 "unexpected option '%.*s'", key_len, key);
+			diag_set(ClientError, errcode, field_no, errmsg);
+			return -1;
+		}
 	}
 	return 0;
 }
@@ -195,7 +200,7 @@ opts_decode(void *opts, const struct opt_def *reg, const char **map,
 		uint32_t key_len;
 		const char *key = mp_decode_str(map, &key_len);
 		if (opts_parse_key(opts, reg, key, key_len, map, errcode,
-				   field_no, region) != 0)
+				   field_no, region, false) != 0)
 			return -1;
 	}
 	return 0;
diff --git a/src/box/opt_def.h b/src/box/opt_def.h
index d1109b65c4..e1149b9820 100644
--- a/src/box/opt_def.h
+++ b/src/box/opt_def.h
@@ -33,6 +33,7 @@
 
 #include "trivia/util.h"
 #include <stddef.h>
+#include <stdbool.h>
 
 #if defined(__cplusplus)
 extern "C" {
@@ -88,12 +89,24 @@ opts_decode(void *opts, const struct opt_def *reg, const char **map,
 	    uint32_t errcode, uint32_t field_no, struct region *region);
 
 /**
- * Populate one options from msgpack-encoded representation
+ * Decode one option and store it into @a opts struct as a field.
+ * @param opts[out] Options to decode to.
+ * @param reg Options definition.
+ * @param key Name of an option.
+ * @param key_len Length of @a key.
+ * @param data Option value.
+ * @param errcode Code of error to set if something is wrong.
+ * @param field_no Field number of an option in a parent element.
+ * @param region Region to allocate OPT_STRPTR option.
+ * @param skip_unknown_options If true, do not set error, if an
+ *        option is unknown. Useful, when it is neccessary to
+ *        allow to store custom fields in options.
  */
 int
 opts_parse_key(void *opts, const struct opt_def *reg, const char *key,
 	       uint32_t key_len, const char **data, uint32_t errcode,
-	       uint32_t field_no, struct region *region);
+	       uint32_t field_no, struct region *region,
+	       bool skip_unknown_options);
 
 #if defined(__cplusplus)
 } /* extern "C" */
diff --git a/test/box/ddl.result b/test/box/ddl.result
index 7076e836ad..3f086f9f67 100644
--- a/test/box/ddl.result
+++ b/test/box/ddl.result
@@ -480,3 +480,28 @@ box.space._collation.index.name:delete{'test'}
 ---
 - [2, 'test', 0, 'ICU', 'ru_RU', {}]
 ...
+--
+-- gh-2839: allow to store custom fields in field definition.
+--
+format = {}
+---
+...
+format[1] = {name = 'field1', type = 'unsigned'}
+---
+...
+format[2] = {'field2', 'unsigned'}
+---
+...
+format[3] = {'field3', 'unsigned', custom_field = 'custom_value'}
+---
+...
+s = box.schema.create_space('test', {format = format})
+---
+...
+s:format()[3].custom_field
+---
+- custom_value
+...
+s:drop()
+---
+...
diff --git a/test/box/ddl.test.lua b/test/box/ddl.test.lua
index f13c54c666..5b1d9dec7e 100644
--- a/test/box/ddl.test.lua
+++ b/test/box/ddl.test.lua
@@ -186,3 +186,14 @@ box.space._collation:select{}
 test_run:cmd('restart server default')
 box.space._collation:select{}
 box.space._collation.index.name:delete{'test'}
+
+--
+-- gh-2839: allow to store custom fields in field definition.
+--
+format = {}
+format[1] = {name = 'field1', type = 'unsigned'}
+format[2] = {'field2', 'unsigned'}
+format[3] = {'field3', 'unsigned', custom_field = 'custom_value'}
+s = box.schema.create_space('test', {format = format})
+s:format()[3].custom_field
+s:drop()
-- 
GitLab