diff --git a/.travis.yml b/.travis.yml index 94339ea..5059e1e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,6 @@ dist: xenial language: php -# gh-151: disable tarantoo-2.2 due to lack of support the new -# _index format. -# # Disable tarantool-1.6 and 1.7 on php 7.1-7.4, because # php-7.[1-4]-cli docker images are based on Debian Buster, but we # have no tarantool-1.[6-7] packages for this distribution. @@ -23,8 +20,8 @@ jobs: env: TARANTOOL_VERSION=1.10 - php: 7.0 env: TARANTOOL_VERSION=2.1 - # - php: 7.0 - # env: TARANTOOL_VERSION=2.2 + - php: 7.0 + env: TARANTOOL_VERSION=2.2 - php: 7.0 env: TARANTOOL_VERSION=2.3 - php: 7.0 @@ -40,8 +37,8 @@ jobs: env: TARANTOOL_VERSION=1.10 - php: 7.1 env: TARANTOOL_VERSION=2.1 - # - php: 7.1 - # env: TARANTOOL_VERSION=2.2 + - php: 7.1 + env: TARANTOOL_VERSION=2.2 - php: 7.1 env: TARANTOOL_VERSION=2.3 - php: 7.1 @@ -58,7 +55,7 @@ jobs: - php: 7.2 env: TARANTOOL_VERSION=2.1 # - php: 7.2 - # env: TARANTOOL_VERSION=2.2 + env: TARANTOOL_VERSION=2.2 - php: 7.2 env: TARANTOOL_VERSION=2.3 - php: 7.2 @@ -75,7 +72,7 @@ jobs: - php: 7.3 env: TARANTOOL_VERSION=2.1 # - php: 7.3 - # env: TARANTOOL_VERSION=2.2 + env: TARANTOOL_VERSION=2.2 - php: 7.3 env: TARANTOOL_VERSION=2.3 - php: 7.3 @@ -92,7 +89,7 @@ jobs: - php: 7.4 env: TARANTOOL_VERSION=2.1 # - php: 7.4 - # env: TARANTOOL_VERSION=2.2 + env: TARANTOOL_VERSION=2.2 - php: 7.4 env: TARANTOOL_VERSION=2.3 - php: 7.4 diff --git a/config.m4 b/config.m4 index c8b5097..8df5eaf 100644 --- a/config.m4 +++ b/config.m4 @@ -1,4 +1,6 @@ dnl config.m4 for extension tarantool +dnl set C compiler to C99 mode +AC_PROG_CC_C99 PHP_ARG_ENABLE(tarantool, for tarantool support, [ --enable-tarantool Enable tarantool support]) diff --git a/src/tarantool_schema.c b/src/tarantool_schema.c index 70d434c..84cc89d 100644 --- a/src/tarantool_schema.c +++ b/src/tarantool_schema.c @@ -69,7 +69,7 @@ schema_index_value_free(const struct schema_index_value *val) { } } -static inline void +static void schema_index_free(struct mh_schema_index_t *schema) { int pos = 0; mh_int_t index_slot = 0; @@ -146,7 +146,7 @@ mh_spacecmp_key_eq( #define MH_DEBUG 1 #include "third_party/mhash.h" -static inline void +static void schema_space_value_free(const struct schema_space_value *val) { if (val) { pefree(val->space_name, 1); @@ -161,7 +161,7 @@ schema_space_value_free(const struct schema_space_value *val) { } } -static inline void +static void schema_space_free(struct mh_schema_space_t *schema) { int pos = 0; mh_int_t space_slot = 0; @@ -198,28 +198,74 @@ schema_space_free(struct mh_schema_space_t *schema) { } } -int parse_field_type(const char *sfield, size_t sfield_len) { - if (sfield_len == 3) { - if (tolower(sfield[0]) == 's' && - tolower(sfield[1]) == 't' && - tolower(sfield[2]) == 'r') - return FT_STR; - if (tolower(sfield[0]) == 'n' && - tolower(sfield[1]) == 'u' && - tolower(sfield[2]) == 'm') - return FT_NUM; +static const char *field_type_strs[] = { + /* [FIELD_TYPE_ANY] = */ "any", + /* [FIELD_TYPE_UNSIGNED] = */ "unsigned", + /* [FIELD_TYPE_STRING] = */ "string", + /* [FIELD_TYPE_NUMBER] = */ "number", + /* [FIELD_TYPE_DOUBLE] = */ "double", + /* [FIELD_TYPE_INTEGER] = */ "integer", + /* [FIELD_TYPE_BOOLEAN] = */ "boolean", + /* [FIELD_TYPE_VARBINARY] = */"varbinary", + /* [FIELD_TYPE_SCALAR] = */ "scalar", + /* [FIELD_TYPE_DECIMAL] = */ "decimal", + /* [FIELD_TYPE_UUID] = */ "uuid", + /* [FIELD_TYPE_ARRAY] = */ "array", + /* [FIELD_TYPE_MAP] = */ "map", +}; + +/** + * Find a string in an array of strings. + */ +static uint32_t +strnindex(const char **haystack, const char *needle, uint32_t len, uint32_t hmax) +{ + if (len == 0) + return hmax; + for (unsigned index = 0; index != hmax && haystack[index]; ++index) { + if (strncasecmp(haystack[index], needle, len) == 0 && + strlen(haystack[index]) == len) + return index; } - return FT_OTHER; + return hmax; +} + +/** + * Strict compare a null-terminated string with a length specified + * string. + */ +static inline bool +strncmp_strict(const char *str, size_t str_len, const char *str_null_term) +{ + return (strncmp(str, str_null_term, str_len) == 0 && + strlen(str_null_term) == str_len); } -static inline int +static enum field_type +field_type_by_name(const char *name, size_t len) +{ + enum field_type field_type = strnindex(field_type_strs, name, len, + field_type_MAX); + if (field_type != field_type_MAX) + return field_type; + /* 'num' and 'str' in _index are deprecated since Tarantool 1.7 */ + if (strncasecmp(name, "num", len) == 0) + return FIELD_TYPE_UNSIGNED; + else if (strncasecmp(name, "str", len) == 0) + return FIELD_TYPE_STRING; + else if (len == 1 && name[0] == '*') + return FIELD_TYPE_ANY; + return field_type_MAX; +} + +static int parse_schema_space_value_value(struct schema_field_value *fld, const char **tuple) { uint32_t sfield_len = 0; if (mp_typeof(**tuple) != MP_STR) goto error; const char *sfield = mp_decode_str(tuple, &sfield_len); - if (memcmp(sfield, "name", sfield_len) == 0) { + if (strncmp_strict(sfield, sfield_len, "name")) { if (mp_typeof(**tuple) != MP_STR) goto error; sfield = mp_decode_str(tuple, &fld->field_name_len); @@ -227,11 +273,15 @@ parse_schema_space_value_value(struct schema_field_value *fld, if (!fld->field_name) goto error; memcpy(fld->field_name, sfield, fld->field_name_len); - } else if (memcmp(sfield, "type", sfield_len) == 0) { + } else if (strncmp_strict(sfield, sfield_len, "type")) { if (mp_typeof(**tuple) != MP_STR) goto error; sfield = mp_decode_str(tuple, &sfield_len); - fld->field_type = parse_field_type(sfield, sfield_len); + fld->field_type = field_type_by_name(sfield, sfield_len); + } else if (strncmp_strict(sfield, sfield_len, "is_nullable")) { + if (mp_typeof(**tuple) != MP_BOOL) + goto error; + fld->is_nullable = mp_decode_bool(tuple); } else { mp_next(tuple); } @@ -240,7 +290,19 @@ parse_schema_space_value_value(struct schema_field_value *fld, return -1; } -static inline int +/** + * Initialization value. + */ +static const struct schema_field_value field_val_def = { + .field_number = 0, + .field_name_len = 0, + .field_name = NULL, + .field_type = field_type_MAX, + .coll_id = COLL_NONE, + .is_nullable = false +}; + +static int parse_schema_space_value(struct schema_space_value *space_string, const char **tuple) { uint32_t fmp_tmp_len = 0; @@ -255,6 +317,7 @@ parse_schema_space_value(struct schema_space_value *space_string, int i = 0; for (i = 0; i < fmt_len; ++i) { struct schema_field_value *val = &(space_string->schema_list[i]); + *val = field_val_def; if (mp_typeof(**tuple) != MP_MAP) goto error; uint32_t arrsz = mp_decode_map(tuple); @@ -269,7 +332,103 @@ parse_schema_space_value(struct schema_space_value *space_string, return -1; } -static inline int +/** + * 1.6.6-1.7.5 + * Decode parts array from tuple field. + */ +static int +decode_index_parts_166(struct schema_field_value *parts, uint32_t part_count, + const char **data) +{ + for (uint32_t i = 0; i < part_count; ++i) { + struct schema_field_value *part = &parts[i]; + *part = field_val_def; + if (mp_typeof(**data) != MP_ARRAY) + return -1; + uint32_t item_count = mp_decode_array(data); + if (item_count < 2) + return -1; + + if (mp_typeof(**data) != MP_UINT) + return -1; + part->field_number = mp_decode_uint(data); + + if (mp_typeof(**data) != MP_STR) + return -1; + uint32_t len; + const char *str = mp_decode_str(data, &len); + part->field_type = field_type_by_name(str, len); + + for (uint32_t j = 2; j < item_count; ++j) + mp_next(data); + } + return 0; +} + +static int +decode_index_part(struct schema_field_value *part, uint32_t map_size, + const char **data) +{ + *part = field_val_def; + for (uint32_t i = 0; i < map_size; ++i) { + if (mp_typeof(**data) != MP_STR) + return -1; + + uint32_t k_len; + const char *key = mp_decode_str(data, &k_len); + if (strncmp_strict(key, k_len, "type")) { + if (mp_typeof(**data) != MP_STR) + return -1; + uint32_t v_len; + const char *val = mp_decode_str(data, &v_len); + part->field_type = field_type_by_name(val, v_len); + } else if (strncmp_strict(key, k_len, "field")) { + if (mp_typeof(**data) != MP_UINT) + return -1; + part->field_number = mp_decode_uint(data); + } else if (strncmp_strict(key, k_len, "collation")) { + if (mp_typeof(**data) != MP_UINT) + return -1; + part->coll_id = mp_decode_uint(data); + } else if (strncmp_strict(key, k_len, "is_nullable")) { + if (mp_typeof(**data) != MP_BOOL) + return -1; + part->is_nullable = mp_decode_bool(data); + } else { + mp_next(data); + } + } + + /* Collation is reasonable only for string and scalar parts. */ + if (part->coll_id != COLL_NONE && + part->field_type != FIELD_TYPE_STRING && + part->field_type != FIELD_TYPE_SCALAR) { + return -1; + } + + return 0; +} + +/** + * Decode parts array from tuple field. + */ +static int +decode_index_parts(struct schema_field_value *parts, uint32_t part_count, + const char **data) +{ + for (uint32_t i = 0; i < part_count; ++i) { + struct schema_field_value *part = &parts[i]; + if (mp_typeof(**data) != MP_MAP) + return -1; + + uint32_t map_size = mp_decode_map(data); + if (decode_index_part(part, map_size, data) != 0) + return -1; + } + return 0; +} + +static int parse_schema_index_value(struct schema_index_value *index_string, const char **tuple) { if (mp_typeof(**tuple) != MP_ARRAY) @@ -283,28 +442,22 @@ parse_schema_index_value(struct schema_index_value *index_string, goto error; memset(index_string->index_parts, 0, part_count * sizeof(struct schema_field_value)); - int i = 0; - for (i = 0; i < part_count; ++i) { - if (mp_typeof(**tuple) != MP_ARRAY) - goto error; - if (mp_decode_array(tuple) != 2) - goto error; - struct schema_field_value *val = &(index_string->index_parts[i]); - if (mp_typeof(**tuple) != MP_UINT) - goto error; - val->field_number = mp_decode_uint(tuple); - uint32_t sfield_len = 0; - if (mp_typeof(**tuple) != MP_STR) - goto error; - const char *sfield = mp_decode_str(tuple, &sfield_len); - val->field_type = parse_field_type(sfield, sfield_len); + + if (mp_typeof(**tuple) == MP_ARRAY) { + /* Base coding format is used. */ + return decode_index_parts_166(index_string->index_parts, + part_count, tuple); + } else { + /* Extended coding format is used. */ + return decode_index_parts(index_string->index_parts, + part_count, tuple); } - return 0; + error: return -1; } -static inline int +static int schema_add_space( struct mh_schema_space_t *schema, const char **data @@ -421,7 +574,7 @@ tarantool_schema_add_spaces( return -1; } -static inline int schema_add_index( +static int schema_add_index( struct mh_schema_space_t *schema, const char **data ) { diff --git a/src/tarantool_schema.h b/src/tarantool_schema.h index 219775a..e3e5e57 100644 --- a/src/tarantool_schema.h +++ b/src/tarantool_schema.h @@ -1,23 +1,46 @@ #ifndef PHP_TNT_SCHEMA_H #define PHP_TNT_SCHEMA_H +#include + struct schema_key { const char *id; uint32_t id_len; uint32_t number; }; +/** + * Possible field data types. + */ enum field_type { - FT_STR = 0, - FT_NUM = 1, - FT_OTHER = 2 + FIELD_TYPE_ANY = 0, + FIELD_TYPE_UNSIGNED, + FIELD_TYPE_STRING, + FIELD_TYPE_NUMBER, + FIELD_TYPE_DOUBLE, + FIELD_TYPE_INTEGER, + FIELD_TYPE_BOOLEAN, + FIELD_TYPE_VARBINARY, + FIELD_TYPE_SCALAR, + FIELD_TYPE_DECIMAL, + FIELD_TYPE_UUID, + FIELD_TYPE_ARRAY, + FIELD_TYPE_MAP, + /* Used for unknown type. */ + field_type_MAX }; +#define COLL_NONE UINT32_MAX + struct schema_field_value { uint32_t field_number; - char *field_name; uint32_t field_name_len; + char *field_name; enum field_type field_type; + /* Collation ID for string comparison. */ + uint32_t coll_id; + /* True if a key part can store NULLs. */ + bool is_nullable; }; struct schema_index_value { diff --git a/test.all.sh b/test.all.sh index 2beb05b..d1c4568 100755 --- a/test.all.sh +++ b/test.all.sh @@ -5,26 +5,18 @@ set -exu # Strict shell (w/o -o pipefail) php_version_list="7.0 7.1 7.2 7.3 7.4" tarantool_version_list="1.6 1.7 1.9 1.10 2.1 2.2 2.3 2.4 2.5" -# gh-151: disable tarantool-2.2 due to lack of support the new -# _index format. -# # Disable tarantool-1.6 and 1.7 on php 7.1-7.4, because # php-7.[1-4]-cli docker images are based on Debian Buster, but we # have no tarantool-1.[6-7] packages for this distribution. exceptions=" - php-7.0-tarantool-2.2 php-7.1-tarantool-1.6 php-7.1-tarantool-1.7 - php-7.1-tarantool-2.2 php-7.2-tarantool-1.6 php-7.2-tarantool-1.7 - php-7.2-tarantool-2.2 php-7.3-tarantool-1.6 php-7.3-tarantool-1.7 - php-7.3-tarantool-2.2 php-7.4-tarantool-1.6 php-7.4-tarantool-1.7 - php-7.4-tarantool-2.2 " for php_version in $php_version_list; do diff --git a/test/shared/box.lua b/test/shared/box.lua index 33838c8..f9e2f42 100755 --- a/test/shared/box.lua +++ b/test/shared/box.lua @@ -134,6 +134,32 @@ box.once('initialization', function() local tuple = yaml.decode(yml)[1] tuple[1] = "12345" box.space._schema:insert(tuple) + + -- gh-151: Support new _index system space format + -- After tarantool-1.7.5-153-g1651fc9be the new _index format + -- was introduced. When an index part uses is_nullable or + -- collation parameter, then the new format will be used. + if tarantool_version_at_least(1, 7, 6, 0) then + local test_index_part_176 = box.schema.space.create( + 'test_index_part_176', + { + format = { + {type = compat.unsigned, name = 'f1'}, + {type = compat.unsigned, name = 'f2', is_nullable = true}, + {type = compat.string, name = 'f3'}, + } + }) + + test_index_part_176:create_index('primary', { + parts = {{1, compat.unsigned}} + }) + test_index_part_176:create_index('secondary', { + parts = { + {2, compat.unsigned, is_nullable = true}, + {3, compat.string, collation = 'unicode_ci'} + } + }) + end end) function test_1()