diff --git a/.gitignore b/.gitignore
index 36d748840d01bba90e3b5f6f72b69aa7a44e37ac..cd2dcca7ddbf1b0076cdb2be49b1f7cf4ae4e596 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@ CMakeCache.txt
 CPackConfig.cmake
 CPackSourceConfig.cmake
 Makefile
+Doxyfile.API
 RPM
 *.src.rpm
 *.i386.rpm
@@ -26,6 +27,8 @@ extra/bin2c
 extra/dist/tarantoolctl.1
 cmake_install.cmake
 config.mk
+doc/doxygen/
+doc/api/
 doc/www/content/doc/dev_guide.html
 doc/www/content/doc/tnt.css
 doc/www/content/doc/user_guide.html
diff --git a/.gitmodules b/.gitmodules
index aba1c2e269cf4a9cdfd2c992154d6485b67fddc0..ea4c4507db8618c3e7a14f9c8bd30844addd8c63 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -10,7 +10,7 @@
 [submodule "sophia"]
        path = third_party/sophia
        url = https://github.com/tarantool/sophia.git
-       branch = dev
+       branch = current
 [submodule "test-run"]
 	path = test-run
 	url = https://github.com/tarantool/test-run.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 61b72dea17a9d8960954fa0968e280102f83ddd7..0e85bc9ab08d14d7720229e93971b1260224ae1e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -113,7 +113,7 @@ add_custom_target(tags COMMAND ctags -R -f tags
 #
 set (CPACK_PACKAGE_VERSION_MAJOR "1")
 set (CPACK_PACKAGE_VERSION_MINOR "6")
-set (CPACK_PACKAGE_VERSION_PATCH "6")
+set (CPACK_PACKAGE_VERSION_PATCH "7")
 
 set (PACKAGE_VERSION "")
 
diff --git a/Doxyfile b/Doxyfile
index 5d73bd8c5e94e603725ffc7bf4c5caf1f161deba..a5669799836c4b15b756c6ea3edda649d03d4438 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -26,7 +26,7 @@ DOXYFILE_ENCODING      = UTF-8
 # identify the project. Note that if you do not use Doxywizard you need
 # to put quotes around the project name if it contains spaces.
 
-PROJECT_NAME           = "Tarantool/Box"
+PROJECT_NAME           = "Tarantool"
 
 # The PROJECT_NUMBER tag can be used to enter a project or revision number.
 # This could be handy for archiving the generated documentation or
@@ -38,14 +38,14 @@ PROJECT_NUMBER         =
 # for a project that appears at the top of each page and should give viewer
 # a quick idea about the purpose of the project. Keep the description short.
 
-PROJECT_BRIEF          = "A transactional NoSQL database"
+PROJECT_BRIEF          = "Get your data in RAM. Get compute close to data. Enjoy the performance."
 
 # With the PROJECT_LOGO tag one can specify an logo or icon that is
 # included in the documentation. The maximum height of the logo should not
 # exceed 55 pixels and the maximum width should not exceed 200 pixels.
 # Doxygen will copy the logo to the output directory.
 
-PROJECT_LOGO           = doc/www-data/logo.png
+PROJECT_LOGO           = doc/www/theme/static/logo.png
 
 # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
 # base path where the generated documentation will be put.
@@ -655,7 +655,7 @@ WARN_LOGFILE           =
 # directories like "/usr/src/myproject". Separate the files or directories
 # with spaces.
 
-INPUT                  = include src
+INPUT                  = src/
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
@@ -1107,7 +1107,7 @@ GENERATE_ECLIPSEHELP   = NO
 # the directory name containing the HTML and XML files should also have
 # this name.
 
-ECLIPSE_DOC_ID         = org.doxygen.Project
+ECLIPSE_DOC_ID         = org.tarantool
 
 # The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs)
 # at top of each HTML page. The value NO (the default) enables the index and
@@ -1115,7 +1115,7 @@ ECLIPSE_DOC_ID         = org.doxygen.Project
 # navigation tree you can set this option to NO if you already set
 # GENERATE_TREEVIEW to YES.
 
-DISABLE_INDEX          = NO
+DISABLE_INDEX          = YES
 
 # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
 # structure should be generated to display hierarchical information.
@@ -1134,7 +1134,7 @@ GENERATE_TREEVIEW      = NO
 # documentation. Note that a value of 0 will completely suppress the enum
 # values from appearing in the overview section.
 
-ENUM_VALUES_PER_LINE   = 4
+ENUM_VALUES_PER_LINE   = 1
 
 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
 # used to set the initial width (in pixels) of the frame in which the tree
@@ -1386,7 +1386,7 @@ MAN_LINKS              = NO
 # generate an XML file that captures the structure of
 # the code including all documentation.
 
-GENERATE_XML           = NO
+GENERATE_XML           = YES
 
 # The XML_OUTPUT tag is used to specify where the XML pages will be put.
 # If a relative path is entered the value of OUTPUT_DIRECTORY will be
@@ -1509,7 +1509,7 @@ INCLUDE_FILE_PATTERNS  =
 # undefined via #undef or recursively expanded use the := operator
 # instead of the = operator.
 
-PREDEFINED             = __attribute__(x)=
+PREDEFINED             = __attribute__(x)= API_EXPORT= LUA_API=
 
 # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
 # this tag can be used to specify a list of macro names that should be expanded.
diff --git a/Doxyfile.API.in b/Doxyfile.API.in
new file mode 100644
index 0000000000000000000000000000000000000000..2faa825ca61e4c77f1384f80f5c05633239e6ca9
--- /dev/null
+++ b/Doxyfile.API.in
@@ -0,0 +1,7 @@
+@INCLUDE = @PROJECT_SOURCE_DIR@/Doxyfile
+INPUT                  = @PROJECT_BINARY_DIR@/src/trivia/tarantool.h
+OUTPUT_DIRECTORY       = @PROJECT_BINARY_DIR@/doc/api/
+ENABLED_SECTIONS       = public
+DISABLE_INDEX          = YES
+GENERATE_TREEVIEW      = NO
+
diff --git a/README.md b/README.md
index 6d72a6d2b97fa3482fcd1ff5badb5f877a327b32..eeede13bffbdee0447f40d8c8b4faa6a1d80f85b 100644
--- a/README.md
+++ b/README.md
@@ -78,7 +78,7 @@ To start the server, try:
 This will start Tarantool in interactive mode.
 
 To run Tarantool regression tests (test/test-run.py),
-a few additional Python modules are ncessary:
+a few additional Python modules are necessary:
  * daemon
  * pyyaml
  * msgpack-python
diff --git a/cmake/compiler.cmake b/cmake/compiler.cmake
index e5d16ae9158f4dacd2d7b3076a5286dbf8f47617..bab0892aed32e81ce8d8ad79704c28ff6a270ac2 100644
--- a/cmake/compiler.cmake
+++ b/cmake/compiler.cmake
@@ -41,7 +41,14 @@ endif()
 if((NOT HAVE_STD_C11 AND NOT HAVE_STD_GNU99) OR
    (NOT HAVE_STD_CXX11 AND NOT HAVE_STD_GNUXX0X))
     set(CMAKE_REQUIRED_FLAGS "-std=c11")
-    check_c_source_compiles("int main(void) { return 0; }" HAVE_STD_C11)
+    check_c_source_compiles("
+    /*
+     * FreeBSD 10 ctype.h header fail to compile on gcc4.8 in c11 mode.
+     * Make sure we aren't affected.
+     */
+    #include <ctype.h>
+    int main(void) { return 0; }
+    " HAVE_STD_C11)
     set(CMAKE_REQUIRED_FLAGS "-std=gnu99")
     check_c_source_compiles("int main(void) { return 0; }" HAVE_STD_GNU99)
     set(CMAKE_REQUIRED_FLAGS "-std=c++11")
diff --git a/cmake/luajit.cmake b/cmake/luajit.cmake
index 06ac79d6a16d1737c3e259c8eceb6bb0e7052d87..39b32410589bec01ba17b45bcc70fd9b1eff6f37 100644
--- a/cmake/luajit.cmake
+++ b/cmake/luajit.cmake
@@ -170,6 +170,7 @@ macro(luajit_build)
         CFLAGS=""
         CXXFLAGS=""
         XCFLAGS="${luajit_xcflags}"
+        CC="${luajit_host_cc}"
         HOST_CC="${luajit_host_cc}"
         TARGET_CC="${luajit_target_cc}"
         CCOPT="${luajit_copt}")
diff --git a/cmake/module.cmake b/cmake/module.cmake
index 8338be7b6c1f1a2944bcc633113a93d997046994..8d6eafbcafe20fcdfff3a82bf68a13e751b80a2f 100644
--- a/cmake/module.cmake
+++ b/cmake/module.cmake
@@ -27,7 +27,7 @@ function(rebuild_module_api)
                 ${CMAKE_CURRENT_SOURCE_DIR}/tarantool_footer.h
         )
 
-    add_custom_target(rebuild_module_api ALL DEPENDS ${srcfiles} ${dstfile})
+    add_custom_target(api ALL DEPENDS ${srcfiles} ${dstfile})
     install(FILES ${dstfile} DESTINATION ${MODULE_INCLUDEDIR})
 endfunction()
 set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/tarantool.h" PROPERTIES GENERATED HEADER_FILE_ONLY)
diff --git a/debian/source/options b/debian/source/options
index 7ec924e4d3a69794df15a9c86448af846d8dfdff..07aeae5c080ebbb75b388011e51c98ff77c95745 100644
--- a/debian/source/options
+++ b/debian/source/options
@@ -1,3 +1,3 @@
 # don't pack some non-free and generated files for Debian
---extend-diff-ignore=rfc4627\.txt|www-data/ycsb|test-run
+--extend-diff-ignore=rfc4627\.txt|www-data/ycsb|test-run|src/lib/msgpuck/debian
 
diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt
index cbe16b27a66c88f44988f60d89398cfbf6f54376..cb4a3e366543ea6b9d7c504c3a905b39a6e0db18 100644
--- a/doc/CMakeLists.txt
+++ b/doc/CMakeLists.txt
@@ -9,5 +9,3 @@ if (ENABLE_DOC)
     add_subdirectory(sphinx)
     add_subdirectory(www)
 endif()
-
-
diff --git a/doc/sphinx/CMakeLists.txt b/doc/sphinx/CMakeLists.txt
index e0b651417f5fb7afa8959d578ae832bbec106d70..1fb237e2c508774994ba472a45c4728f579c1927 100644
--- a/doc/sphinx/CMakeLists.txt
+++ b/doc/sphinx/CMakeLists.txt
@@ -1,7 +1,16 @@
 find_package(Sphinx REQUIRED)
-
+find_package(Doxygen REQUIRED)
 #find_program(MKDIR mkdir)
 
+configure_file("${PROJECT_SOURCE_DIR}/Doxyfile.API.in"
+    "${PROJECT_BINARY_DIR}/Doxyfile.API")
+
+add_custom_target(api-doc
+    COMMAND ${DOXYGEN_EXECUTABLE} "${PROJECT_BINARY_DIR}/Doxyfile.API"
+    WORKING_DIRECTORY "${PROJECT_BINARY_DIR}"
+    DEPENDS api
+    COMMENT "Generating API documentation" VERBATIM)
+
 set(SPHINX_BUILD_SINGLE_DIR "${PROJECT_BINARY_DIR}/doc/sphinx/_single_build/")
 set(SPHINX_BUILD_HTML_DIR   "${PROJECT_BINARY_DIR}/doc/sphinx/_html_build/")
 set(SPHINX_HTML_DIR  "${PROJECT_BINARY_DIR}/doc/www/output/doc/")
@@ -20,6 +29,7 @@ add_custom_target(sphinx-html ALL
         "${PROJECT_SOURCE_DIR}/doc/sphinx"
         "${SPHINX_HTML_DIR}"
     COMMENT "Building HTML documentation with Sphinx"
+    DEPENDS api-doc
 )
 
 add_custom_target(sphinx-singlehtml ALL
@@ -31,4 +41,5 @@ add_custom_target(sphinx-singlehtml ALL
         "${SPHINX_HTML_DIR}"
         singlehtml.rst
     COMMENT "Building HTML documentation with Sphinx"
+    DEPENDS api-doc
 )
diff --git a/doc/sphinx/book/box/box_index.rst b/doc/sphinx/book/box/box_index.rst
index 95ece5c1bfa6cbd4bb738bf1f22d3309d835e23b..95d7d8458eb632e43d5f766b57e8a82acb2fa84b 100644
--- a/doc/sphinx/book/box/box_index.rst
+++ b/doc/sphinx/book/box/box_index.rst
@@ -455,7 +455,7 @@ API is a direct binding to corresponding methods of index objects of type
                                     field-values, or a tuple containing only
                                     the field-values.
 
-        :return: the number of matching index keys. The ``index`` function
+        :return: the number of matching index keys. The ``count`` function
                 is only applicable for the memtx storage engine.
         :rtype:  number
 
diff --git a/doc/sphinx/book/box/box_space.rst b/doc/sphinx/book/box/box_space.rst
index 5390b1bacea3cbb7f06b08379228d3761258ab56..23c1ac28329fb7edcfd42b860ca5c1317234c44c 100644
--- a/doc/sphinx/book/box/box_space.rst
+++ b/doc/sphinx/book/box/box_space.rst
@@ -8,7 +8,7 @@ The ``box.space`` package has the data-manipulation functions ``select``,
 ``insert``, ``replace``, ``update``, ``delete``, ``get``, ``put``. It also has
 members, such as id, and whether or not a space is enabled. Package source code
 is available in file
-`src/box/lua/box.lua <https://github.com/tarantool/tarantool/blob/master/src/box/lua/schema.lua>`_.
+`src/box/lua/schema.lua <https://github.com/tarantool/tarantool/blob/master/src/box/lua/schema.lua>`_.
 
 A list of all ``box.space`` functions follows, then comes a list of all
 ``box.space`` members.
@@ -447,10 +447,6 @@ A list of all ``box.space`` functions follows, then comes a list of all
 
     space-object:len()
 
-        .. NOTE::
-
-            The ``len()`` function is only applicable for the memtx storage engine.
-
         :return: Number of tuples in the space.
 
         | :codebold:`Example`
diff --git a/doc/sphinx/book/box/index.rst b/doc/sphinx/book/box/index.rst
index a4481d185118f487bb45c998e23df1b139920239..458a72ad79349fd0d24852c21ebecac0946ccc1b 100644
--- a/doc/sphinx/book/box/index.rst
+++ b/doc/sphinx/book/box/index.rst
@@ -434,16 +434,12 @@ Every one of the examples does the same thing:
 select a tuple set from a space named tester where
 the primary-key field value equals 1.
 
-First there are "naming variations":
-
-    1. :codenormal:`box.space.tester:select{1}`
-
-    2. :codenormal:`box.space['tester']:select{1}`
-
-    3. :codenormal:`box.space[512]:select{1}`
-
-    4. :codenormal:`variable = 'tester'; box.space[variable]:select{1}`
-
+First there are "naming variations": |br|
+|nbsp| 1. :codenormal:`box.space.tester:select{1}` |br|
+|nbsp| 2. :codenormal:`box.space['tester']:select{1}` |br|
+|nbsp| 3. :codenormal:`box.space[512]:select{1}` |br|
+|nbsp| 4. :codenormal:`variable = 'tester'` |br|
+|nbsp| |nbsp| |nbsp| :codenormal:`box.space[variable]:select{1}` |br|
 ... There is an assumption that the numeric id of
 'tester' is 512, which happens to be the case in our
 sandbox example only. Literal values such as 'tester'
@@ -452,20 +448,15 @@ in this manual have the "box.space.space.tester:" form;
 however, this is a matter of user preference and all
 the variants exist in the wild.
 
-Then there are "parameter variations":
-
-    1. :codenormal:`box.space.tester:select{1}`
-
-    2. :codenormal:`box.space.tester:select({1})`
-
-    3. :codenormal:`box.space.tester:select(1)`
-
-    4. :codenormal:`box.space.tester:select({1},{iterator='EQ'})`
-
-    5. :codenormal:`variable = 1; box.space.tester:select{variable}`
-
-    6. :codenormal:`variable = {1}; box.space.tester:select(variable)`
-
+Then there are "parameter variations": |br|
+|nbsp| 1. :codenormal:`box.space.tester:select{1}` |br|
+|nbsp| 2. :codenormal:`box.space.tester:select({1})` |br|
+|nbsp| 3. :codenormal:`box.space.tester:select(1)` |br|
+|nbsp| 4. :codenormal:`box.space.tester:select({1},{iterator='EQ'})` |br|
+|nbsp| 5. :codenormal:`variable = 1` |br|
+|nbsp| |nbsp| |nbsp| :codenormal:`box.space.tester:select{variable}` |br|
+|nbsp| 6. :codenormal:`variable = {1}` |br|
+|nbsp| |nbsp| |nbsp| :codenormal:`box.space.tester:select(variable)` |br|
 ... The primary-key value is enclosed in braces, and if
 it was a multi-part primary key then the value would be
 multi-part, for example "...select{1,2,3}". The braces
diff --git a/doc/sphinx/book/box/limitations.rst b/doc/sphinx/book/box/limitations.rst
index 945ed7de6ac8694419bd31bd996830b0cc784ab8..47b375bd0703e8adb8b856d0352e574b371e8e68 100644
--- a/doc/sphinx/book/box/limitations.rst
+++ b/doc/sphinx/book/box/limitations.rst
@@ -39,10 +39,9 @@ Length of an index name or space name or user name
     32 (``box.schema.NAME_MAX``).
 
 Limitations which are only applicable for the sophia storage engine
-    The maximum number of fields in an index is always 1, that is, multi-part
-    indexes are not supported. The maximum number of indexes in a space is
+    The maximum number of indexes in a space is
     always 1, that is, secondary indexes are not supported. Indexes must be
-    unique, that is, the options type=HASH or type=RTREE or type=BITSET are
+    type=TREE, that is, the options type=HASH or type=RTREE or type=BITSET are
     not supported. Indexes must be unique, that is, the option unique=false
-    is not supported. The ``alter()``, ``len()``, and ``count()`` functions
+    is not supported. The ``alter()`` and ``count()`` functions
     are not supported.
diff --git a/doc/sphinx/book/configuration/cfg-basic.rst b/doc/sphinx/book/configuration/cfg-basic.rst
index b5b322c4125deb0f65d712eba8695be213d9a279..9e9f4f0347ee784942a74c794e8c37c3271161d4 100644
--- a/doc/sphinx/book/configuration/cfg-basic.rst
+++ b/doc/sphinx/book/configuration/cfg-basic.rst
@@ -40,7 +40,7 @@
 .. confval:: sophia_dir
 
     A directory where sophia files will be stored. Can be relative to
-    :confval:`work_dir`. If not specified, defaults to :file:`work_dir/sophia`.
+    :confval:`work_dir`. If not specified, defaults to :file:`work_dir`.
 
     Type: string |br|
     Default: "sophia" |br|
diff --git a/doc/sphinx/conf.py b/doc/sphinx/conf.py
index ecae307fabd13deb1bb021b428ebbc9f5d5bf616..35e50f363fdfc0b901757c5b4e70702fe433503e 100644
--- a/doc/sphinx/conf.py
+++ b/doc/sphinx/conf.py
@@ -12,13 +12,17 @@ extensions = [
     'sphinx.ext.todo',
     'sphinx.ext.ifconfig',
     'ext.custom',
-    'ext.lua'
+    'ext.lua',
+    'breathe'
 ]
 primary_domain = 'lua'
 templates_path = ['../_templates']
 source_suffix = '.rst'
 
 project = u'Tarantool'
+breathe_projects = {
+    "api":"../../api/xml",
+}
 
 # |release| The full version, including alpha/beta/rc tags.
 release = open('../../../VERSION').read().strip()
diff --git a/doc/sphinx/dev_guide/building_from_source.rst b/doc/sphinx/dev_guide/building_from_source.rst
index 98cc8d426c132dfe27d5534abb8b6253c0d06316..caa081d430143d834e5a5dbb5dce6384e7a24aaa 100644
--- a/doc/sphinx/dev_guide/building_from_source.rst
+++ b/doc/sphinx/dev_guide/building_from_source.rst
@@ -25,15 +25,17 @@ explain what the steps are, then on the Internet you can look at some example sc
    Here are names of tools and libraries which may have to be installed in advance,
    using ``sudo apt-get`` (for Ubuntu), ``sudo yum install`` (for CentOS), or the
    equivalent on other platforms. Different platforms may use slightly different
-   names. Do not worry about the ones marked `optional, only in Mac OS scripts`
-   unless your platform is Mac OS.
+   names. Ignore the ones marked `optional, only in Mac OS scripts`
+   unless the platform is Mac OS. Ignore the one marked `optional,
+   only for documentation` unless the intent is to use the ``-DENABLE_DOC`` option in step 5.
 
    * **gcc and g++, or clang**                # see above
    * **git**                                  # see above
    * **cmake**                                # see above
-   * **libreadline-dev or libreadline6-dev**  # for interactive mode
+   * **libreadline-dev or libreadline6-dev or readline-devel**  # for interactive mode
    * **autoconf**                             # optional, only in Mac OS scripts
    * **zlib1g** or **zlib**                   # optional, only in Mac OS scripts
+   * **doxygen**                              # optional, only for documentation
 
 2. Set up python modules for running the test suite or creating documentation.
    This step is optional. Python modules are not necessary for building Tarantool
@@ -41,7 +43,8 @@ explain what the steps are, then on the Internet you can look at some example sc
    "Run the test suite" option in step 7. Say: |br|
    :codenormal:`python --version` |br|
    You should see that the python version is greater than 2.6 --
-   preferably 2.7 -- and less than 3.0
+   preferably 2.7 -- and less than 3.0.
+   It may be necessary to install python first.
 
    On Ubuntu you can get modules from the repository:
 
@@ -54,7 +57,7 @@ explain what the steps are, then on the Internet you can look at some example sc
      # For documentation
      sudo apt-get install python-sphinx python-pelican python-beautifulsoup
 
-   On CentOS too you can get modules from the repository:
+   On CentOS 6 too you can get modules from the repository:
 
    .. code-block:: bash
 
@@ -66,6 +69,9 @@ explain what the steps are, then on the Internet you can look at some example sc
 
    .. code-block:: bash
 
+     # On some machines this initial command may be necessary:
+     # wget https://bootstrap.pypa.io/ez_setup.py -O - | sudo python
+
      # python module for parsing YAML (pyYAML): For test suite:
      # (If wget fails, check the http://pyyaml.org/wiki/PyYAML
      # to see what the current version is.)
@@ -94,6 +100,9 @@ explain what the steps are, then on the Internet you can look at some example sc
 
    Finally, use Python :code:`pip` to bring in Python packages
    that may not be up-to-date in the distro repositories.
+   (On CentOS 7 it will be necessary to install pip first,
+   with :code:`sudo yum install epel-release` followed by
+   :code:`sudo yum install python-pip`.)
 
    .. code-block:: bash
 
@@ -101,6 +110,7 @@ explain what the steps are, then on the Internet you can look at some example sc
      pip install tarantool\>0.4 --user
      # For documentation
      sudo pip install pelican
+     sudo pip install breathe
      sudo pip install -U sphinx
 
 3. Use :code:`git` to download the latest source code from the
diff --git a/doc/sphinx/getting_started.rst b/doc/sphinx/getting_started.rst
index e36f84a6353fd66dfc16c137de8c35e77ee48850..dcd45fad51dd3d626186cd17af5ee044f576abd9 100644
--- a/doc/sphinx/getting_started.rst
+++ b/doc/sphinx/getting_started.rst
@@ -72,7 +72,7 @@ variable which will contain the Debian version code e.g. "Wheezy":
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 There is always an up-to-date Ubuntu repository at
-http://tarantool.org/dist/master/ubuntu The repository contains builds for
+http://tarantool.org/dist/master/ubuntu. The repository contains builds for
 Ubuntu 12.04 "precise", 13.10 "saucy", and 14.04 "trusty". Add the tarantool.org
 repository to your apt sources list. $release is an environment variable which
 will contain the Ubuntu version code e.g. "precise". If you want the version
@@ -107,17 +107,15 @@ your x86 platform:
 * http://tarantool.org/dist/master/centos/7/os/x86_64 for version 7, x86-64
 
 Add the following section to your yum repository list
-(``/etc/yum.repos.d/tarantool.repo``) (in the following instructions, ``$releasever``
+(``/etc/yum.repos.d/tarantool.repo``) (in these instructions ``$releasever``
 i.e. CentOS release version must be either 6 or 7 and ``$basearch`` i.e. base
 architecture must be either i386 or x86_64):
 
-.. code-block:: ini
-
-    # [tarantool]
-    name=CentOS-$releasever - Tarantool
-    baseurl=http://tarantool.org/dist/master/centos/$releasever/os/$basearch/
-    enabled=1
-    gpgcheck=0
+    | :samp:`# [tarantool]`
+    | :samp:`name=CentOS-$releasever - Tarantool`
+    | :samp:`baseurl=http://tarantool.org/dist/master/centos/{$releasever}/os/{$basearch}/`
+    | :samp:`enabled=1`
+    | :samp:`gpgcheck=0`
 
 For example, if you have CentOS version 6 and x86-64, you can add the new section thus:
 
@@ -131,6 +129,8 @@ For example, if you have CentOS version 6 and x86-64, you can add the new sectio
     echo "enabled=1" | sudo tee -a /etc/yum.repos.d/tarantool.repo
     echo "gpgcheck=0" | sudo tee -a /etc/yum.repos.d/tarantool.repo
 
+Then install with :code:`sudo yum install tarantool`.
+
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                           Fedora
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -138,17 +138,15 @@ For example, if you have CentOS version 6 and x86-64, you can add the new sectio
 These instructions are applicable for Fedora 19, 20 or rawhide. Pick the Fedora
 repository, for example http://tarantool.org/dist/master/fedora/20/x86_64 for
 version 20, x86-64. Add the following section to your yum repository list
-(``/etc/yum.repos.d/tarantool.repo``) (in the following instructions,
+(``/etc/yum.repos.d/tarantool.repo``) (in these instructions
 ``$releasever`` i.e. Fedora release version must be 19, 20 or rawhide and
 ``$basearch`` i.e. base architecture must be x86_64):
 
-.. code-block:: ini
-
-    [tarantool]
-    name=Fedora-$releasever - Tarantool
-    baseurl=http://tarantool.org/dist/master/fedora/$releasever$basearch/
-    enabled=1
-    gpgcheck=0
+    | :samp:`[tarantool]`
+    | :samp:`name=Fedora-$releasever - Tarantool`
+    | :samp:`baseurl=http://tarantool.org/dist/master/fedora/{$releasever}/{$basearch}/`
+    | :samp:`enabled=1`
+    | :samp:`gpgcheck=0`
 
 For example, if you have Fedora version 20, you can add the new section thus:
 
@@ -161,13 +159,14 @@ For example, if you have Fedora version 20, you can add the new section thus:
     sudo tee -a /etc/yum.repos.d/tarantool.repo
     echo "enabled=1" | sudo tee -a /etc/yum.repos.d/tarantool.repo
     echo "gpgcheck=0" | sudo tee -a /etc/yum.repos.d/tarantool.repo
-    Then install with sudo yum install tarantool.
+
+Then install with :code:`sudo yum install tarantool`.
 
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                           Gentoo
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-available from tarantool portage overlay. Use layman to add the overlay to your system:
+There is a tarantool portage overlay. Use layman to add the overlay to your system:
 
 .. code-block:: bash
 
diff --git a/doc/sphinx/reference/capi.rst b/doc/sphinx/reference/capi.rst
new file mode 100644
index 0000000000000000000000000000000000000000..b4832ed084aad8d2cabba20e75bdec03221da7af
--- /dev/null
+++ b/doc/sphinx/reference/capi.rst
@@ -0,0 +1,6 @@
+-------------------------------------------------------------------------------
+                            Module C API
+-------------------------------------------------------------------------------
+
+.. doxygenfile:: tarantool.h
+   :project: api
diff --git a/doc/sphinx/reference/index.rst b/doc/sphinx/reference/index.rst
index 9025ef6a01d36bf5d070729255023a5f70cde2ff..44c3b83d16d216af1c0580ff899455ab15495fb1 100644
--- a/doc/sphinx/reference/index.rst
+++ b/doc/sphinx/reference/index.rst
@@ -25,4 +25,5 @@
     expirationd
     shard
     tarantool
+    capi
 
diff --git a/doc/www/content/newsite/index.yml b/doc/www/content/newsite/index.yml
index 83fcd48238e3517e65fb5a51d7a9670bb495e311..1bafc7cacd52bfc5a805459cc841a191be5e4922 100644
--- a/doc/www/content/newsite/index.yml
+++ b/doc/www/content/newsite/index.yml
@@ -31,6 +31,11 @@ blocks  :
     - "asynchronous master-master replication"
     - "authentication and access control"
   news:
+    -
+      - "Tarantool 1.6.6 is released"
+      - "https://groups.google.com/forum/#!topic/tarantool/4-RwTCVp2uQ"
+      - "28.08"
+      - "2015"
     -
       - "Tarantool 1.6.5 is released"
       - "https://groups.google.com/d/msg/tarantool/zIM68T93Be0/nqwe6If74PwJ"
@@ -41,11 +46,6 @@ blocks  :
       - "http://www.meetup.com/Tarantool/events/220924229/"
       - "04.03"
       - "2015"
-    -
-      - "Tarantool 1.6.4 is released"
-      - "https://groups.google.com/forum/#!topic/tarantool/F3qc_zemEYg"
-      - "24.11"
-      - "2014"
   support:
     - format: rst
       content: >
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 25171da8e81e9bc7c61afb01af6990cdbe196a12..4c8a081130d49b44da45f019be827f997c8c0deb 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -24,7 +24,6 @@ lua_source(lua_sources lua/console.lua)
 lua_source(lua_sources lua/bsdsocket.lua)
 lua_source(lua_sources lua/errno.lua)
 lua_source(lua_sources lua/log.lua)
-lua_source(lua_sources lua/net_box.lua)
 lua_source(lua_sources lua/help.lua)
 lua_source(lua_sources lua/help_en_US.lua)
 lua_source(lua_sources lua/tap.lua)
@@ -84,7 +83,7 @@ set (server_sources
      iobuf.cc
      coio_buf.cc
      pickle.cc
-     stat.cc
+     rmean.cc
      ipc.cc
      latch.cc
      errinj.cc
@@ -97,7 +96,6 @@ set (server_sources
      cpu_feature.c
      fiob.c
      tt_uuid.c
-     ffisyms.cc
      uri.c
      coeio_file.cc
      lua/digest.cc
@@ -106,7 +104,6 @@ set (server_sources
      lua/trigger.cc
      lua/ipc.cc
      lua/msgpack.cc
-     lua/net_box.cc
      lua/utils.cc
      lua/errno.c
      lua/bsdsocket.cc
@@ -168,7 +165,7 @@ add_subdirectory(box)
 set(TARANTOOL_C_FLAGS ${CMAKE_C_FLAGS} PARENT_SCOPE)
 set(TARANTOOL_CXX_FLAGS ${CMAKE_CXX_FLAGS} PARENT_SCOPE)
 
-add_executable(tarantool main.cc)
+add_executable(tarantool main.cc ffisyms.cc)
 add_dependencies(tarantool build_bundled_libs)
 target_link_libraries(tarantool box ${common_libraries})
 
diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index b46f631a7d5e21d6388040ba72e3c0e64c339211..bcd9e17b4dc5034528cac527633fdea309f4cca6 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -8,6 +8,7 @@ lua_source(lua_sources lua/schema.lua)
 lua_source(lua_sources lua/tuple.lua)
 lua_source(lua_sources lua/session.lua)
 lua_source(lua_sources lua/snapshot_daemon.lua)
+lua_source(lua_sources lua/net_box.lua)
 set(bin_sources)
 bin_source(bin_sources bootstrap.snap bootstrap.h)
 
@@ -29,6 +30,7 @@ add_library(box
     tuple_update.cc
     key_def.cc
     index.cc
+    memtx_index.cc
     memtx_hash.cc
     memtx_tree.cc
     memtx_rtree.cc
@@ -67,6 +69,7 @@ add_library(box
     lua/stat.cc
     lua/error.cc
     lua/session.cc
+    lua/net_box.cc
     ${bin_sources})
 
 target_link_libraries(box ${sophia_lib})
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 13d6cd4f03b0ab168b38473362727b8f86713dc5..6187135588e0dae9f8fa2cecb9d76bd89af7c104 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -33,6 +33,7 @@
 #include "user_def.h"
 #include "user.h"
 #include "space.h"
+#include "memtx_index.h"
 #include "func.h"
 #include "txn.h"
 #include "tuple.h"
@@ -104,52 +105,66 @@ access_check_ddl(uint32_t owner_uid)
 	}
 }
 
+const char *
+map_field_get(const char *map, const char *needle, int needle_type,
+	      int map_field_no)
+{
+	if (map == NULL || mp_typeof(*map) != MP_MAP) {
+		tnt_raise(ClientError, ER_FIELD_TYPE,
+			  map_field_no + INDEX_OFFSET, "map");
+	}
+	uint32_t map_size = mp_decode_map(&map);
+	for (uint32_t i = 0; i < map_size; i++) {
+		const char *key;
+		uint32_t len;
+		if (mp_typeof(*map) != MP_STR) {
+			tnt_raise(ClientError, ER_FIELD_TYPE,
+				  map_field_no + INDEX_OFFSET,
+				  "map key type");
+		}
+		key = mp_decode_str(&map, &len);
+		if (len != strlen(needle) ||
+		    strncasecmp(key, needle, len) != 0) {
+
+			mp_next(&map);
+			continue;
+		}
+		if (mp_typeof(*map) != needle_type) {
+			tnt_raise(ClientError, ER_FIELD_TYPE,
+				  map_field_no + INDEX_OFFSET,
+				  "map value type");
+		}
+		return map;
+	}
+	return NULL;
+}
 
 /** Fill key_opts structure from opts field in tuple of space _index */
 void
-key_opts_create_from_field(struct key_opts *opts, const char *field)
+key_opts_create_from_field(struct key_opts *opts, const char *map)
 {
-	key_opts_create(opts);
-	if (field == NULL)
+	*opts = key_opts_default;
+	if (map == NULL)
 		return;
-	if (mp_typeof(*field) != MP_MAP) {
-		tnt_raise(ClientError, ER_FIELD_TYPE, INDEX_OPTS + INDEX_OFFSET,
-			  "map");
-	}
-	uint32_t map_size = mp_decode_map(&field);
-	for (uint32_t i = 0; i < map_size; i++) {
-		const char *key;
+	const char *field;
+
+	if ((field = map_field_get(map, "unique", MP_BOOL, INDEX_OPTS)))
+		opts->is_unique = mp_decode_bool(&field);
+	if ((field = map_field_get(map, "dimension", MP_UINT, INDEX_OPTS)))
+		opts->dimension = mp_decode_uint(&field);
+	if ((field = map_field_get(map, "distance", MP_STR, INDEX_OPTS))) {
 		uint32_t len;
-		if (mp_typeof(*field) != MP_STR) {
+		const char *distance = mp_decode_str(&field, &len);
+		distance = tuple_field_to_cstr(distance, len);
+
+		enum rtree_index_distance_type distance_type =
+			STR2ENUM(rtree_index_distance_type, distance);
+		if (distance_type == rtree_index_distance_type_MAX) {
 			tnt_raise(ClientError,
-				  ER_FIELD_TYPE, INDEX_OPTS + INDEX_OFFSET,
-				  "string");
-		}
-		key = mp_decode_str(&field, &len);
-		if (len == strlen("unique") &&
-		    memcmp(key, "unique", len) == 0) {
-
-
-			if (mp_typeof(*field) != MP_BOOL) {
-				tnt_raise(ClientError,
-					  ER_FIELD_TYPE,
-					  INDEX_OPTS + INDEX_OFFSET,
-					  "bool");
-			}
-			opts->is_unique = mp_decode_bool(&field);
-		} else if (len == strlen("dimension") &&
-			   memcmp(key, "dimension", len) == 0) {
-
-			if (mp_typeof(*field) != MP_UINT) {
-				tnt_raise(ClientError,
-					  ER_FIELD_TYPE,
-					  INDEX_OPTS + INDEX_OFFSET,
-					  "unsigned");
-			}
-			opts->dimension = mp_decode_uint(&field);
-		} else {
-			mp_next(&field);
+				  ER_UNKNOWN_RTREE_INDEX_DISTANCE_TYPE,
+				  distance);
 		}
+		opts->distance = distance_type;
 	}
 }
 
@@ -185,10 +200,16 @@ key_def_new_from_tuple(struct tuple *tuple)
 		part_count = mp_decode_array(&parts);
 	} else {
 		/* 1.6.5 _index space structure */
-		key_opts_create(&opts);
+		opts = key_opts_default;
 		opts.is_unique = tuple_field_u32(tuple, INDEX_165_IS_UNIQUE);
 		part_count = tuple_field_u32(tuple, INDEX_165_PART_COUNT);
 	}
+	/**
+	 * XXX this is an ugly clutch in absence of Lua-level
+	 * index-type specific defaults.
+	 */
+	if (opts.dimension == 0 && type == RTREE)
+		opts.dimension = 2;
 	key_def = key_def_new(id, index_id, name, type, &opts, part_count);
 	auto scoped_guard = make_scoped_guard([=] { key_def_delete(key_def); });
 
@@ -528,8 +549,7 @@ alter_space_do(struct txn *txn, struct alter_space *alter,
 	 * snapshot/xlog, but needs to continue staying "fully
 	 * built".
 	 */
-	alter->new_space->handler->replace =
-		alter->old_space->handler->replace;
+	alter->new_space->handler->onAlter(alter->old_space->handler);
 
 	memcpy(alter->new_space->access, alter->old_space->access,
 	       sizeof(alter->old_space->access));
@@ -891,9 +911,9 @@ AddIndex::alter(struct alter_space *alter)
 	Index *new_index = index_find(alter->new_space, new_key_def->iid);
 
 	/* Now deal with any kind of add index during normal operation. */
-	struct iterator *it = pk->position();
+	struct iterator *it = pk->allocIterator();
+	IteratorGuard guard(it);
 	pk->initIterator(it, ITER_ALL, NULL, 0);
-	IteratorGuard it_guard(it);
 
 	/*
 	 * The index has to be built tuple by tuple, since
@@ -902,8 +922,6 @@ AddIndex::alter(struct alter_space *alter)
 	 * added to the index (insufficient number of fields,
 	 * etc., the build is aborted.
 	 */
-	new_index->beginBuild();
-	new_index->endBuild();
 	/* Build the new index. */
 	struct tuple *tuple;
 	struct tuple_format *format = alter->new_space->format;
@@ -1185,16 +1203,16 @@ space_has_data(uint32_t id, uint32_t iid, uint32_t uid)
 	if (space == NULL)
 		return false;
 
-	Index *index = space_index(space, iid);
-	if (index == NULL)
+	if (space_index(space, iid) == NULL)
 		return false;
-	struct iterator *it = index->position();
+
+	MemtxIndex *index = index_find_system(space, iid);
 	char key[6];
 	assert(mp_sizeof_uint(BOX_SYSTEM_ID_MIN) <= sizeof(key));
 	mp_encode_uint(key, uid);
+	struct iterator *it = index->position();
 
 	index->initIterator(it, ITER_EQ, key, 1);
-	IteratorGuard it_guard(it);
 	if (it->next(it))
 		return true;
 	return false;
diff --git a/src/box/box.cc b/src/box/box.cc
index 9a89724200aefd021fa6f8ae252355e3164bf025..364100b839b02b35da500ed998c109446601c9d1 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -36,7 +36,7 @@
 #include "recovery.h"
 #include "relay.h"
 #include "replica.h"
-#include <stat.h>
+#include <rmean.h>
 #include "main.h"
 #include "tuple.h"
 #include "lua/call.h"
@@ -44,6 +44,7 @@
 #include "schema.h"
 #include "engine.h"
 #include "memtx_engine.h"
+#include "memtx_index.h"
 #include "sysview_engine.h"
 #include "sophia_engine.h"
 #include "space.h"
@@ -57,34 +58,60 @@
 #include "cluster.h" /* replica */
 #include "replica.h"
 
-static void process_ro(struct request *request, struct port *port);
-box_process_func box_process = process_ro;
-
 struct recovery_state *recovery;
 
 static struct replica replica_buf; /* used to store the single instance */
 
 bool snapshot_in_progress = false;
 static bool box_init_done = false;
+bool is_ro = true;
 
-static void
-process_ro(struct request *request, struct port *port)
+void
+process_rw(struct request *request, struct tuple **result)
 {
-	if (!iproto_type_is_select(request->type))
-		tnt_raise(LoggedError, ER_READONLY);
-	return process_rw(request, port);
+	assert(iproto_type_is_dml(request->type));
+	rmean_collect(rmean_box, request->type, 1);
+	try {
+		struct space *space = space_cache_find(request->space_id);
+		struct txn *txn = txn_begin_stmt(request, space);
+		access_check_space(space, PRIV_W);
+		struct tuple *tuple;
+		switch (request->type) {
+		case IPROTO_INSERT:
+		case IPROTO_REPLACE:
+			tuple = space->handler->executeReplace(txn, space, request);
+			break;
+		case IPROTO_UPDATE:
+			tuple = space->handler->executeUpdate(txn, space, request);
+			break;
+		case IPROTO_DELETE:
+			tuple = space->handler->executeDelete(txn, space, request);
+			break;
+		case IPROTO_UPSERT:
+			space->handler->executeUpsert(txn, space, request);
+			/** fall through */
+		default:
+			 tuple = NULL;
+
+		}
+		if (result)
+			*result = tuple;
+	} catch (Exception *e) {
+		txn_rollback_stmt();
+		throw;
+	}
 }
 
 void
 box_set_ro(bool ro)
 {
-	box_process = ro ? process_ro : process_rw;
+	is_ro = ro;
 }
 
 bool
 box_is_ro(void)
 {
-	return box_process == process_ro;
+	return is_ro;
 }
 
 static void
@@ -99,7 +126,7 @@ recover_row(struct recovery_state *r, void *param, struct xrow_header *row)
 	request_decode(&request, (const char *) row->body[0].iov_base,
 		row->body[0].iov_len);
 	request.header = row;
-	process_rw(&request, &null_port);
+	process_rw(&request, NULL);
 }
 
 /* {{{ configuration bindings */
@@ -304,7 +331,7 @@ boxk(enum iproto_type type, uint32_t space_id, const char *format, ...)
 	assert(data <= buf + sizeof(buf));
 	req.tuple = buf;
 	req.tuple_end = data;
-	process_rw(&req, &null_port);
+	process_rw(&req, NULL);
 }
 
 int
@@ -363,23 +390,15 @@ box_index_id_by_name(uint32_t space_id, const char *name, uint32_t len)
 }
 /** \endcond public */
 
-static inline int
+int
 box_process1(struct request *request, box_tuple_t **result)
 {
-	struct port_buf port_buf;
-	port_buf_create(&port_buf);
 	try {
-		box_process(request, &port_buf.base);
-		box_tuple_t *tuple = NULL;
-		/* Sophia: always bless tuple even if result is NULL */
-		if (port_buf.first != NULL)
-			tuple = tuple_bless(port_buf.first->tuple);
-		port_buf_destroy(&port_buf);
-		if (result)
-			*result = tuple;
+		if (is_ro)
+			tnt_raise(LoggedError, ER_READONLY);
+		process_rw(request, result);
 		return 0;
 	} catch (Exception *e) {
-		port_buf_destroy(&port_buf);
 		return -1;
 	}
 }
@@ -389,18 +408,19 @@ box_select(struct port *port, uint32_t space_id, uint32_t index_id,
 	   int iterator, uint32_t offset, uint32_t limit,
 	   const char *key, const char *key_end)
 {
-	struct request request;
-	request_create(&request, IPROTO_SELECT);
-	request.space_id = space_id;
-	request.index_id = index_id;
-	request.limit = limit;
-	request.offset = offset;
-	request.iterator = iterator;
-	request.key = key;
-	request.key_end = key_end;
+	rmean_collect(rmean_box, IPROTO_SELECT, 1);
 
 	try {
-		box_process(&request, port);
+		struct space *space = space_cache_find(space_id);
+		struct txn *txn = in_txn();
+		access_check_space(space, PRIV_R);
+		space->handler->executeSelect(txn, space, index_id, iterator,
+					      offset, limit, key, key_end, port);
+		if (txn == NULL) {
+			/* no txn is created, so simply collect garbage here */
+			fiber_gc();
+		}
+		port_eof(port);
 		return 0;
 	} catch (Exception *e) {
 		/* will be hanled by box.error() in Lua */
@@ -503,10 +523,9 @@ box_on_cluster_join(const tt_uuid *server_uuid)
 {
 	/** Find the largest existing server id. */
 	struct space *space = space_cache_find(BOX_CLUSTER_ID);
-	class Index *index = index_find(space, 0);
+	class MemtxIndex *index = index_find_system(space, 0);
 	struct iterator *it = index->position();
 	index->initIterator(it, ITER_LE, NULL, 0);
-	IteratorGuard it_guard(it);
 	struct tuple *tuple = it->next(it);
 	/** Assign a new server id. */
 	uint32_t server_id = tuple ? tuple_field_u32(tuple, 0) + 1 : 1;
@@ -587,7 +606,8 @@ box_free(void)
 		tuple_free();
 		port_free();
 		engine_shutdown();
-		stat_free();
+		rmean_delete(rmean_error);
+		rmean_delete(rmean_box);
 	}
 }
 
@@ -611,6 +631,25 @@ engine_init()
 	engine_register(sophia);
 }
 
+/**
+ * @brief Reduce the current number of threads in the thread pool to the
+ * bare minimum. Doesn't prevent the pool from spawning new threads later
+ * if demand mounts.
+ */
+static void
+thread_pool_trim()
+{
+	/*
+	 * Trim OpenMP thread pool.
+	 * Though we lack the direct control the workaround below works for
+	 * GNU OpenMP library. The library stops surplus threads on entering
+	 * a parallel region. Can't go below 2 threads due to the
+	 * implementation quirk.
+	 */
+#pragma omp parallel num_threads(2)
+	;
+}
+
 static inline void
 box_init(void)
 {
@@ -619,8 +658,8 @@ box_init(void)
 		   cfg_geti("slab_alloc_maximal"),
 		   cfg_getd("slab_alloc_factor"));
 
-	stat_init();
-	stat_base = stat_register(iproto_type_strs, IPROTO_TYPE_STAT_MAX);
+	rmean_box = rmean_new(iproto_type_strs, IPROTO_TYPE_STAT_MAX);
+	rmean_error = rmean_new(rmean_error_strings, RMEAN_ERROR_LAST);
 
 	engine_init();
 
@@ -692,7 +731,12 @@ box_init(void)
 
 	engine_end_recovery();
 
-	stat_cleanup(stat_base, IPROTO_TYPE_STAT_MAX);
+	/*
+	 * Recovery inflates the thread pool quite a bit (due to parallel sort).
+	 */
+	thread_pool_trim();
+
+	rmean_cleanup(rmean_box);
 
 	if (source != NULL) {
 		if (replica == NULL) {
diff --git a/src/box/box.h b/src/box/box.h
index 3ca093176c1f0b6d57c2d033a47cdcf8a4df493c..0f8038730ffbfb7dccf2f8a03969cba45f00aa79 100644
--- a/src/box/box.h
+++ b/src/box/box.h
@@ -53,16 +53,6 @@ void box_free(void);
 void
 box_atfork();
 
-/**
- * The main entry point to the
- * Box: callbacks into the request processor.
- * These are function pointers since they can
- * change when entering/leaving read-only mode
- * (master->slave propagation).
- */
-typedef void (*box_process_func)(struct request *request, struct port *port);
-/** For read-write operations. */
-extern box_process_func box_process;
 
 void
 box_set_ro(bool ro);
@@ -133,33 +123,142 @@ box_select(struct port *port, uint32_t space_id, uint32_t index_id,
 	   const char *key, const char *key_end);
 
 /** \cond public */
+
+/*
+ * Opaque structure passed to the stored C procedure
+ */
 typedef struct box_function_ctx box_function_ctx_t;
+
+/**
+ * Return a tuple from stored C procedure.
+ *
+ * Returned tuple is automatically reference counted by Tarantool.
+ *
+ * \param ctx an opaque structure passed to the stored C procedure by
+ * Tarantool
+ * \param tuple a tuple to return
+ * \retval -1 on error (perhaps, out of memory; check box_error_last())
+ * \retval 0 otherwise
+ */
 API_EXPORT int
 box_return_tuple(box_function_ctx_t *ctx, box_tuple_t *tuple);
 
+/**
+ * Find space id by name.
+ *
+ * This function performs SELECT request to _vspace system space.
+ * \param name space name
+ * \param len length of \a name
+ * \retval BOX_ID_NIL on error or if not found (check box_error_last())
+ * \retval space_id otherwise
+ * \sa box_index_id_by_name
+ */
 API_EXPORT uint32_t
 box_space_id_by_name(const char *name, uint32_t len);
 
+/**
+ * Find index id by name.
+ *
+ * This function performs SELECT request to _vindex system space.
+ * \param space_id space identifier
+ * \param name index name
+ * \param len length of \a name
+ * \retval BOX_ID_NIL on error or if not found (check box_error_last())
+ * \retval index_id otherwise
+ * \sa box_space_id_by_name
+ */
 API_EXPORT uint32_t
 box_index_id_by_name(uint32_t space_id, const char *name, uint32_t len);
 
+/**
+ * Execute an INSERT request.
+ *
+ * \param space_id space identifier
+ * \param tuple encoded tuple in MsgPack Array format ([ field1, field2, ...])
+ * \param tuple_end end of @a tuple
+ * \param[out] result a new tuple. Can be set to NULL to discard result.
+ * \retval -1 on error (check box_error_last())
+ * \retval 0 on success
+ * \sa \code box.space[space_id]:insert(tuple) \endcode
+ */
 API_EXPORT int
 box_insert(uint32_t space_id, const char *tuple, const char *tuple_end,
 	   box_tuple_t **result);
 
+/**
+ * Execute an REPLACE request.
+ *
+ * \param space_id space identifier
+ * \param tuple encoded tuple in MsgPack Array format ([ field1, field2, ...])
+ * \param tuple_end end of @a tuple
+ * \param[out] result a new tuple. Can be set to NULL to discard result.
+ * \retval -1 on error (check box_error_last())
+ * \retval 0 on success
+ * \sa \code box.space[space_id]:replace(tuple) \endcode
+ */
 API_EXPORT int
 box_replace(uint32_t space_id, const char *tuple, const char *tuple_end,
 	    box_tuple_t **result);
 
+/**
+ * Execute an DELETE request.
+ *
+ * \param space_id space identifier
+ * \param index_id index identifier
+ * \param key encoded key in MsgPack Array format ([part1, part2, ...]).
+ * \param key_end the end of encoded \a key.
+ * \param[out] result an old tuple. Can be set to NULL to discard result.
+ * \retval -1 on error (check box_error_last())
+ * \retval 0 on success
+ * \sa \code box.space[space_id].index[index_id]:delete(key) \endcode
+ */
 API_EXPORT int
 box_delete(uint32_t space_id, uint32_t index_id, const char *key,
 	   const char *key_end, box_tuple_t **result);
 
+/**
+ * Execute an UPDATE request.
+ *
+ * \param space_id space identifier
+ * \param index_id index identifier
+ * \param key encoded key in MsgPack Array format ([part1, part2, ...]).
+ * \param key_end the end of encoded \a key.
+ * \param ops encoded operations in MsgPack Arrat format, e.g.
+ * [ [ '=', field_id,  value ],  ['!', 2, 'xxx'] ]
+ * \param ops_end the end of encoded \a ops
+ * \param index_base 0 if field_ids in update operations are zero-based
+ * indexed (like C) or 1 if for one-based indexed field ids (like Lua).
+ * \param[out] result a new tuple. Can be set to NULL to discard result.
+ * \retval -1 on error (check box_error_last())
+ * \retval 0 on success
+ * \sa \code box.space[space_id].index[index_id]:update(key, ops) \endcode
+ * \sa box_upsert()
+ */
 API_EXPORT int
 box_update(uint32_t space_id, uint32_t index_id, const char *key,
 	   const char *key_end, const char *ops, const char *ops_end,
 	   int index_base, box_tuple_t **result);
 
+/**
+ * Execute an UPSERT request.
+ *
+ * \param space_id space identifier
+ * \param index_id index identifier
+ * \param key encoded key in MsgPack Array format ([part1, part2, ...]).
+ * \param key_end the end of encoded \a key.
+ * \param ops encoded operations in MsgPack Arrat format, e.g.
+ * [ [ '=', field_id,  value ],  ['!', 2, 'xxx'] ]
+ * \param ops_end the end of encoded \a ops
+ * \param tuple encoded tuple in MsgPack Array format ([ field1, field2, ...])
+ * \param tuple_end end of @a tuple
+ * \param index_base 0 if field_ids in update operations are zero-based
+ * indexed (like C) or 1 if for one-based indexed field ids (like Lua).
+ * \param[out] result a new tuple. Can be set to NULL to discard result.
+ * \retval -1 on error (check box_error_last())
+ * \retval 0 on success
+ * \sa \code box.space[space_id].index[index_id]:update(key, ops) \endcode
+ * \sa box_update()
+ */
 API_EXPORT int
 box_upsert(uint32_t space_id, uint32_t index_id, const char *key,
 	   const char *key_end, const char *ops, const char *ops_end,
@@ -168,4 +267,14 @@ box_upsert(uint32_t space_id, uint32_t index_id, const char *key,
 
 /** \endcond public */
 
+/**
+ * The main entry point to the
+ * Box: callbacks into the request processor.
+ * These are function pointers since they can
+ * change when entering/leaving read-only mode
+ * (master->slave propagation).
+ */
+int
+box_process1(struct request *request, box_tuple_t **result);
+
 #endif /* INCLUDES_TARANTOOL_BOX_H */
diff --git a/src/box/engine.cc b/src/box/engine.cc
index b04a0ae155de3298f4cfadf37292510f1d54bde1..cd342f6616e630797e37c34c129fdba468e3d4dd 100644
--- a/src/box/engine.cc
+++ b/src/box/engine.cc
@@ -28,14 +28,20 @@
  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
+#include "tuple.h"
+#include "txn.h"
+#include "port.h"
+#include "request.h"
 #include "engine.h"
 #include "space.h"
 #include "exception.h"
 #include "schema.h"
 #include "salad/rlist.h"
+#include "scoped_guard.h"
 #include <stdlib.h>
 #include <string.h>
 #include <latch.h>
+#include <errinj.h>
 
 RLIST_HEAD(engines);
 
@@ -85,11 +91,136 @@ bool Engine::needToBuildSecondaryKey(struct space * /* space */)
 	return true;
 }
 
+void
+Engine::beginJoin()
+{
+}
+
+int
+Engine::beginCheckpoint(int64_t lsn)
+{
+	(void) lsn;
+	return 0;
+}
+
+int
+Engine::waitCheckpoint()
+{
+	return 0;
+}
+
+void
+Engine::commitCheckpoint()
+{
+}
+
+void
+Engine::abortCheckpoint()
+{
+}
+
+void
+Engine::endRecovery()
+{
+}
+
+void
+Engine::recoverToCheckpoint(int64_t /* lsn */)
+{
+}
+
+void
+Engine::join(Relay *relay)
+{
+	(void) relay;
+}
+
+void
+Engine::dropIndex(Index *index)
+{
+	(void) index;
+}
+
+void
+Engine::keydefCheck(struct space *space, struct key_def *key_def)
+{
+	(void) space;
+	(void) key_def;
+	/*
+	 * Don't bother checking key_def to match the view requirements.
+	 * Index::initIterator() must check key on each call.
+	 */
+}
+
 Handler::Handler(Engine *f)
 	:engine(f)
 {
 }
 
+struct tuple *
+Handler::executeReplace(struct txn *, struct space *,
+                        struct request *)
+{
+	tnt_raise(ClientError, ER_UNSUPPORTED, engine->name, "replace");
+}
+
+struct tuple *
+Handler::executeDelete(struct txn*, struct space *, struct request *)
+{
+	tnt_raise(ClientError, ER_UNSUPPORTED, engine->name, "delete");
+}
+
+struct tuple *
+Handler::executeUpdate(struct txn*, struct space *, struct request *)
+{
+	tnt_raise(ClientError, ER_UNSUPPORTED, engine->name, "update");
+}
+
+void
+Handler::executeUpsert(struct txn *, struct space *, struct request *)
+{
+	tnt_raise(ClientError, ER_UNSUPPORTED, engine->name, "upsert");
+}
+
+void
+Handler::onAlter(Handler *)
+{
+}
+
+void
+Handler::executeSelect(struct txn *, struct space *space,
+		       uint32_t index_id, uint32_t iterator,
+		       uint32_t offset, uint32_t limit,
+		       const char *key, const char * /* key_end */,
+		       struct port *port)
+{
+	Index *index = index_find(space, index_id);
+
+	uint32_t found = 0;
+	if (iterator >= iterator_type_MAX)
+		tnt_raise(IllegalParams, "Invalid iterator type");
+	enum iterator_type type = (enum iterator_type) iterator;
+
+	uint32_t part_count = key ? mp_decode_array(&key) : 0;
+	key_validate(index->key_def, type, key, part_count);
+
+	struct iterator *it = index->allocIterator();
+	IteratorGuard guard(it);
+	index->initIterator(it, type, key, part_count);
+
+	struct tuple *tuple;
+	while ((tuple = it->next(it)) != NULL) {
+		TupleGuard tuple_gc(tuple);
+		if (offset > 0) {
+			offset--;
+			continue;
+		}
+		if (limit == found++)
+			break;
+		port_add_tuple(port, tuple);
+	}
+}
+
 /** Register engine instance. */
 void engine_register(Engine *engine)
 {
diff --git a/src/box/engine.h b/src/box/engine.h
index 5fe6086747d2bd7ef8ff87f5ef16a409c691c983..2b881ab78d0802057eef048821e81655df4f22fd 100644
--- a/src/box/engine.h
+++ b/src/box/engine.h
@@ -32,23 +32,13 @@
  */
 #include "index.h"
 
+struct request;
 struct space;
 struct tuple;
 class Relay;
 
 enum engine_flags {
 	ENGINE_CAN_BE_TEMPORARY = 1,
-	/**
-	 * Identifies that engine can handle changes
-	 * of primary key during update.
-	 * During update operation user could change primary
-	 * key of a tuple, which is prohibited, to avoid funny
-	 * effects during replication. Some engines can
-	 * track down this situation and abort the operation;
-	 * such engines should set this flag.
-	 * If the flag is not set, the server will verify
-	 * that the primary key is not changed.
-	 */
 	ENGINE_AUTO_CHECK_UPDATE = 2,
 };
 
@@ -69,17 +59,17 @@ class Engine: public Object {
 	virtual void init();
 	/** Create a new engine instance for a space. */
 	virtual Handler *open() = 0;
-	virtual void initSystemSpace(struct space *space);
-	/**
-	 * Check a key definition for violation of
-	 * various limits.
-	 */
-	virtual void keydefCheck(struct space *space, struct key_def*) = 0;
 	/**
 	 * Create an instance of space index. Used in alter
 	 * space.
 	 */
 	virtual Index *createIndex(struct key_def*) = 0;
+	virtual void initSystemSpace(struct space *space);
+	/**
+	 * Check a key definition for violation of
+	 * various limits.
+	 */
+	virtual void keydefCheck(struct space *space, struct key_def*);
 	/**
 	 * Called by alter when a primary key added,
 	 * after createIndex is invoked for the new
@@ -89,7 +79,7 @@ class Engine: public Object {
 	/**
 	 * Delete all tuples in the index on drop.
 	 */
-	virtual void dropIndex(Index *) = 0;
+	virtual void dropIndex(Index *);
 	/**
 	 * Called by alter when the primary key is dropped.
 	 * Do whatever is necessary with space/handler object,
@@ -104,7 +94,7 @@ class Engine: public Object {
 	 */
 	virtual bool needToBuildSecondaryKey(struct space *space);
 
-	virtual void join(Relay *) = 0;
+	virtual void join(Relay *);
 	/**
 	 * Begin a new statement in an existing or new
 	 * transaction.
@@ -142,35 +132,36 @@ class Engine: public Object {
 	 * After that the engine will be given rows
 	 * from the binary log to replay.
 	 */
-	virtual void recoverToCheckpoint(int64_t checkpoint_id) = 0;
+	virtual void recoverToCheckpoint(int64_t checkpoint_id);
 	/**
 	 * Inform the engine about the end of recovery from the
 	 * binary log.
 	 */
-	virtual void endRecovery() = 0;
+	virtual void endRecovery();
 	/**
 	 * Notify engine about a JOIN start (slave-side)
 	 */
-	virtual void beginJoin() = 0;
+	virtual void beginJoin();
 	/**
 	 * Begin a two-phase snapshot creation in this
 	 * engine (snapshot is a memtx idea of a checkpoint).
 	 */
-	virtual int beginCheckpoint(int64_t) = 0;
+	virtual int beginCheckpoint(int64_t);
 	/**
 	 * Wait for a checkpoint to complete. The LSN
 	 * must match one in begin_checkpoint().
 	 */
-	virtual int waitCheckpoint() = 0;
+	virtual int waitCheckpoint();
 	/**
 	 * All engines prepared their checkpoints,
 	 * fix up the changes.
 	 */
-	virtual void commitCheckpoint() = 0;
+	virtual void commitCheckpoint();
 	/**
 	 * An error in one of the engines, abort checkpoint.
 	 */
-	virtual void abortCheckpoint() = 0;
+	virtual void abortCheckpoint();
+
 public:
 	/** Name of the engine. */
 	const char *name;
@@ -189,92 +180,27 @@ struct Handler: public Object {
 	Handler(Engine *f);
 	virtual ~Handler() {}
 
-	/**
-	 * @brief A single method to handle REPLACE, DELETE and UPDATE.
-	 *
-	 * @param sp space
-	 * @param old_tuple the tuple that should be removed (can be NULL)
-	 * @param new_tuple the tuple that should be inserted (can be NULL)
-	 * @param mode      dup_replace_mode, used only if new_tuple is not
-	 *                  NULL and old_tuple is NULL, and only for the
-	 *                  primary key.
-	 *
-	 * For DELETE, new_tuple must be NULL. old_tuple must be
-	 * previously found in the primary key.
-	 *
-	 * For REPLACE, old_tuple must be NULL. The additional
-	 * argument dup_replace_mode further defines how REPLACE
-	 * should proceed.
-	 *
-	 * For UPDATE, both old_tuple and new_tuple must be given,
-	 * where old_tuple must be previously found in the primary key.
-	 *
-	 * Let's consider these three cases in detail:
-	 *
-	 * 1. DELETE, old_tuple is not NULL, new_tuple is NULL
-	 *    The effect is that old_tuple is removed from all
-	 *    indexes. dup_replace_mode is ignored.
-	 *
-	 * 2. REPLACE, old_tuple is NULL, new_tuple is not NULL,
-	 *    has one simple sub-case and two with further
-	 *    ramifications:
-	 *
-	 *	A. dup_replace_mode is DUP_INSERT. Attempts to insert the
-	 *	new tuple into all indexes. If *any* of the unique indexes
-	 *	has a duplicate key, deletion is aborted, all of its
-	 *	effects are removed, and an error is thrown.
-	 *
-	 *	B. dup_replace_mode is DUP_REPLACE. It means an existing
-	 *	tuple has to be replaced with the new one. To do it, tries
-	 *	to find a tuple with a duplicate key in the primary index.
-	 *	If the tuple is not found, throws an error. Otherwise,
-	 *	replaces the old tuple with a new one in the primary key.
-	 *	Continues on to secondary keys, but if there is any
-	 *	secondary key, which has a duplicate tuple, but one which
-	 *	is different from the duplicate found in the primary key,
-	 *	aborts, puts everything back, throws an exception.
-	 *
-	 *	For example, if there is a space with 3 unique keys and
-	 *	two tuples { 1, 2, 3 } and { 3, 1, 2 }:
-	 *
-	 *	This REPLACE/DUP_REPLACE is OK: { 1, 5, 5 }
-	 *	This REPLACE/DUP_REPLACE is not OK: { 2, 2, 2 } (there
-	 *	is no tuple with key '2' in the primary key)
-	 *	This REPLACE/DUP_REPLACE is not OK: { 1, 1, 1 } (there
-	 *	is a conflicting tuple in the secondary unique key).
-	 *
-	 *	C. dup_replace_mode is DUP_REPLACE_OR_INSERT. If
-	 *	there is a duplicate tuple in the primary key, behaves the
-	 *	same way as DUP_REPLACE, otherwise behaves the same way as
-	 *	DUP_INSERT.
-	 *
-	 * 3. UPDATE has to delete the old tuple and insert a new one.
-	 *    dup_replace_mode is ignored.
-	 *    Note that old_tuple primary key doesn't have to match
-	 *    new_tuple primary key, thus a duplicate can be found.
-	 *    For this reason, and since there can be duplicates in
-	 *    other indexes, UPDATE is the same as DELETE +
-	 *    REPLACE/DUP_INSERT.
-	 *
-	 * @return old_tuple. DELETE, UPDATE and REPLACE/DUP_REPLACE
-	 * always produce an old tuple. REPLACE/DUP_INSERT always returns
-	 * NULL. REPLACE/DUP_REPLACE_OR_INSERT may or may not find
-	 * a duplicate.
-	 *
-	 * The method is all-or-nothing in all cases. Changes are either
-	 * applied to all indexes, or nothing applied at all.
-	 *
-	 * Note, that even in case of REPLACE, dup_replace_mode only
-	 * affects the primary key, for secondary keys it's always
-	 * DUP_INSERT.
-	 *
-	 * The call never removes more than one tuple: if
-	 * old_tuple is given, dup_replace_mode is ignored.
-	 * Otherwise, it's taken into account only for the
-	 * primary key.
-	 */
-	engine_replace_f replace;
+	virtual struct tuple *
+	executeReplace(struct txn *, struct space *,
+		       struct request *);
+	virtual struct tuple *
+	executeDelete(struct txn *, struct space *,
+		      struct request *);
+	virtual struct tuple *
+	executeUpdate(struct txn *, struct space *,
+		      struct request *);
+	virtual void
+	executeUpsert(struct txn *, struct space *,
+		      struct request *);
 
+	virtual void
+	executeSelect(struct txn *, struct space *,
+		      uint32_t index_id, uint32_t iterator,
+		      uint32_t offset, uint32_t limit,
+		      const char *key, const char *key_end,
+		      struct port *);
+
+	virtual void onAlter(Handler *old);
 	Engine *engine;
 };
 
@@ -296,12 +222,6 @@ engine_can_be_temporary(uint32_t flags)
 	return flags & ENGINE_CAN_BE_TEMPORARY;
 }
 
-static inline bool
-engine_auto_check_update(uint32_t flags)
-{
-	return flags & ENGINE_AUTO_CHECK_UPDATE;
-}
-
 static inline uint32_t
 engine_id(Handler *space)
 {
diff --git a/src/box/errcode.h b/src/box/errcode.h
index 13a776a7242f59cba8fcc84c4f939ec0f5041597..57008e1b1f1405ed50fac53c4c0732f0d91b762d 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -156,6 +156,7 @@ struct errcode_record {
 	/*100 */_(ER_FUNCTION_LANGUAGE,		2, "Unsupported language '%s' specified for function '%s'") \
 	/*101 */_(ER_RTREE_RECT,		2, "RTree: %s must be an array with %u (point) or %u (rectangle/box) numeric coordinates") \
 	/*102 */_(ER_PROC_C,			2, "%s") \
+	/*103 */_(ER_UNKNOWN_RTREE_INDEX_DISTANCE_TYPE,	2, "Unknown RTREE index distance type %s") \
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/box/error.cc b/src/box/error.cc
index 789ffc6f6287941fb7fbb7e0d06b9f9180d14e6f..d50c0f33d2614e7e21afd80c9cabc316b92f110b 100644
--- a/src/box/error.cc
+++ b/src/box/error.cc
@@ -32,6 +32,11 @@
 #include <stdio.h>
 
 #include <fiber.h>
+struct rmean *rmean_error = NULL;
+
+const char *rmean_error_strings[RMEAN_ERROR_LAST] = {
+	"ERROR"
+};
 
 static struct method clienterror_methods[] = {
 	make_method(&type_ClientError, "code", &ClientError::errcode),
@@ -49,6 +54,8 @@ ClientError::ClientError(const char *file, unsigned line,
 	va_start(ap, errcode);
 	vsnprintf(m_errmsg, sizeof(m_errmsg),
 		  tnt_errcode_desc(m_errcode), ap);
+	if (rmean_error)
+		rmean_collect(rmean_error, RMEAN_ERROR, 1);
 	va_end(ap);
 }
 
@@ -59,6 +66,8 @@ ClientError::ClientError(const char *file, unsigned line, const char *msg,
 	m_errcode = errcode;
 	strncpy(m_errmsg, msg, sizeof(m_errmsg) - 1);
 	m_errmsg[sizeof(m_errmsg) - 1] = 0;
+	if (rmean_error)
+		rmean_collect(rmean_error, RMEAN_ERROR, 1);
 }
 
 void
diff --git a/src/box/error.h b/src/box/error.h
index 1e3deab61be4ee4e725fff40a113ef180a165c5b..9aa96b4ffaf29a9884251059850216c03ee62000 100644
--- a/src/box/error.h
+++ b/src/box/error.h
@@ -32,6 +32,15 @@
  */
 #include "errcode.h"
 #include "exception.h"
+#include "rmean.h"
+
+extern struct rmean *rmean_error;
+
+enum rmean_error_name {
+	RMEAN_ERROR,
+	RMEAN_ERROR_LAST
+};
+extern const char *rmean_error_strings[RMEAN_ERROR_LAST];
 
 extern const struct type type_ClientError;
 class ClientError: public Exception {
@@ -86,45 +95,77 @@ class ErrorInjection: public LoggedError {
 };
 
 /** \cond public */
+
 struct box_error;
+/**
+ * Error - contains information about error.
+ */
 typedef struct box_error box_error_t;
 
 /**
- * Return error type, e.g. "ClientError", "SocketError", etc.
+ * Return the error type, e.g. "ClientError", "SocketError", etc.
+ * \param error
+ * \return not-null string
  */
 API_EXPORT const char *
 box_error_type(const box_error_t *error);
 
-/*
+/**
  * Return IPROTO error code
+ * \param error error
+ * \return enum box_error_code
  */
 API_EXPORT uint32_t
 box_error_code(const box_error_t *error);
 
-/*
- * Return error message
+/**
+ * Return the error message
+ * \param error error
+ * \return not-null string
  */
 API_EXPORT const char *
 box_error_message(const box_error_t *error);
 
 /**
- * Return last error
+ * Get the information about the last API call error.
+ *
+ * The Tarantool error handling works most like libc's errno. All API calls
+ * return -1 or NULL in the event of error. An internal pointer to
+ * box_error_t type is set by API functions to indicate what went wrong.
+ * This value is only significant if API call failed (returned -1 or NULL).
+ *
+ * Successed function can also touch last error in some cases. You don't
+ * have to clear last error before calling API functions. The returned object
+ * is valid only until next call to **any** API function.
+ *
+ * You must set the last error using box_error_raise() in your stored C
+ * procedures if you want to return custom error message. You can re-throw
+ * the last API error to IPROTO client by keeping the current value and
+ * returning -1 to Tarantool from your stored procedure.
+ *
+ * \return last error.
  */
 API_EXPORT const box_error_t *
 box_error_last(void);
 
-/*
- * Clear last error
+/**
+ * Clear the last error.
  */
 API_EXPORT void
 box_error_clear(void);
 
-/*
- * Set last error
- * \param code IPROTO error code
+/**
+ * Set the last error.
+ *
+ * \param code IPROTO error code (enum \link box_error_code \endlink)
+ * \param format (const char * ) - printf()-like format string
+ * \param ... - format arguments
+ * \returns -1 for convention use
+ *
+ * \sa enum box_error_code
  */
 API_EXPORT int
-box_error_raise(uint32_t code, const char *fmt, ...);
+box_error_raise(uint32_t code, const char *format, ...);
 /** \endcond public */
 
 #endif /* TARANTOOL_BOX_ERROR_H_INCLUDED */
diff --git a/src/box/index.cc b/src/box/index.cc
index fba8082c5209a1e6c667b381f710dd9d20cebfd8..b0519d774590ee3df1699d1e81f3f71d1f236820 100644
--- a/src/box/index.cc
+++ b/src/box/index.cc
@@ -34,6 +34,8 @@
 #include "schema.h"
 #include "user_def.h"
 #include "space.h"
+#include "iproto_constants.h"
+#include "request.h"
 
 const char *iterator_type_strs[] = {
 	/* [ITER_EQ]  = */ "EQ",
@@ -145,32 +147,11 @@ primary_key_validate(struct key_def *key_def, const char *key,
 
 Index::Index(struct key_def *key_def_arg)
 	:key_def(key_def_dup(key_def_arg)),
-	sc_version(::sc_version),
-	m_position(NULL)
-{}
-
-void
-Index::beginBuild()
-{}
-
-void
-Index::reserve(uint32_t /* size_hint */)
-{}
-
-void
-Index::buildNext(struct tuple *tuple)
-{
-	replace(NULL, tuple, DUP_INSERT);
-}
-
-void
-Index::endBuild()
+	sc_version(::sc_version)
 {}
 
 Index::~Index()
 {
-	if (m_position != NULL)
-		m_position->free(m_position);
 	key_def_delete(key_def);
 }
 
@@ -178,26 +159,26 @@ size_t
 Index::size() const
 {
 	tnt_raise(ClientError, ER_UNSUPPORTED,
-		  index_type_strs[key_def->type],
-		  "size()");
+	          index_type_strs[key_def->type],
+	          "size()");
 }
 
 struct tuple *
-Index::min(const char *key, uint32_t part_count) const
+Index::min(const char* /* key */, uint32_t /* part_count */) const
 {
-	struct iterator *it = position();
-	initIterator(it, ITER_GE, key, part_count);
-	IteratorGuard guard(it);
-	return it->next(it);
+	tnt_raise(ClientError, ER_UNSUPPORTED,
+		  index_type_strs[key_def->type],
+		  "min()");
+	return NULL;
 }
 
 struct tuple *
-Index::max(const char *key, uint32_t part_count) const
+Index::max(const char* /* key */, uint32_t /* part_count */) const
 {
-	struct iterator *it = position();
-	initIterator(it, ITER_LE, key, part_count);
-	IteratorGuard guard(it);
-	return it->next(it);
+	tnt_raise(ClientError, ER_UNSUPPORTED,
+		  index_type_strs[key_def->type],
+		  "max()");
+	return NULL;
 }
 
 struct tuple *
@@ -211,18 +192,13 @@ Index::random(uint32_t rnd) const
 }
 
 size_t
-Index::count(enum iterator_type type, const char *key, uint32_t part_count) const
+Index::count(enum iterator_type /* type */, const char* /* key */,
+             uint32_t /* part_count */) const
 {
-	if (type == ITER_ALL && key == NULL)
-		return size(); /* optimization */
-	struct iterator *it = position();
-	initIterator(it, type, key, part_count);
-	IteratorGuard guard(it);
-	size_t count = 0;
-	struct tuple *tuple = NULL;
-	while ((tuple = it->next(it)) != NULL)
-		++count;
-	return count;
+	tnt_raise(ClientError, ER_UNSUPPORTED,
+		  index_type_strs[key_def->type],
+		  "count()");
+	return 0;
 }
 
 struct tuple *
@@ -267,32 +243,6 @@ Index::destroyReadViewForIterator(struct iterator *iterator)
 		  "consistent read view");
 }
 
-void
-index_build(Index *index, Index *pk)
-{
-	uint32_t n_tuples = pk->size();
-	uint32_t estimated_tuples = n_tuples * 1.2;
-
-	index->beginBuild();
-	index->reserve(estimated_tuples);
-
-	if (n_tuples > 0) {
-		say_info("Adding %" PRIu32 " keys to %s index '%s' ...",
-			 n_tuples, index_type_strs[index->key_def->type],
-			 index_name(index));
-	}
-
-	struct iterator *it = pk->position();
-	pk->initIterator(it, ITER_ALL, NULL, 0);
-	IteratorGuard it_guard(it);
-
-	struct tuple *tuple;
-	while ((tuple = it->next(it)))
-		index->buildNext(tuple);
-
-	index->endBuild();
-}
-
 static inline Index *
 check_index(uint32_t space_id, uint32_t index_id)
 {
@@ -309,7 +259,7 @@ tuple_bless_null(struct tuple *tuple)
 	return NULL;
 }
 
-size_t
+ssize_t
 box_index_len(uint32_t space_id, uint32_t index_id)
 {
 	try {
@@ -319,7 +269,7 @@ box_index_len(uint32_t space_id, uint32_t index_id)
 	}
 }
 
-size_t
+ssize_t
 box_index_bsize(uint32_t space_id, uint32_t index_id)
 {
        try {
@@ -357,6 +307,9 @@ box_index_get(uint32_t space_id, uint32_t index_id, const char *key,
 		uint32_t part_count = key ? mp_decode_array(&key) : 0;
 		primary_key_validate(index->key_def, key, part_count);
 		struct tuple *tuple = index->findByKey(key, part_count);
+		/* Count statistics */
+		rmean_collect(rmean_box, IPROTO_SELECT, 1);
+
 		*result = tuple_bless_null(tuple);
 		return 0;
 	}  catch (Exception *) {
@@ -492,8 +445,6 @@ box_iterator_next(box_iterator_t *itr, box_tuple_t **result)
 void
 box_iterator_free(box_iterator_t *it)
 {
-	if (it->close)
-		it->close(it);
 	if (it->free)
 		it->free(it);
 }
diff --git a/src/box/index.h b/src/box/index.h
index 1e03b8f728e338a7554e235cd6aebb05d37b4412..a97c1d9a2f32231ac5e524320494124fe5e03beb 100644
--- a/src/box/index.h
+++ b/src/box/index.h
@@ -38,10 +38,10 @@
 
 /** \cond public */
 typedef struct tuple box_tuple_t;
+/** A space iterator */
 typedef struct iterator box_iterator_t;
 
 /**
- * @abstract Iterator type
  * Controls how to iterate over tuples in an index.
  * Different index types support different iterator types.
  * For example, one can start iteration from a particular value
@@ -79,14 +79,42 @@ enum iterator_type {
 	iterator_type_MAX     = ITER_NEIGHBOR + 1
 };
 
+/**
+ * Allocate and initialize iterator for space_id, index_id.
+ *
+ * A returned iterator must be destroyed by box_iterator_free().
+ *
+ * \param space_id space identifier.
+ * \param index_id index identifier.
+ * \param type \link iterator_type iterator type \endlink
+ * \param key encoded key in MsgPack Array format ([part1, part2, ...]).
+ * \param key_end the end of encoded \a key
+ * \retval NULL on error (check box_error_last())
+ * \retval iterator otherwise
+ * \sa box_iterator_next()
+ * \sa box_iterator_free()
+ */
 API_EXPORT box_iterator_t *
 box_index_iterator(uint32_t space_id, uint32_t index_id, int type,
 		   const char *key, const char *key_end);
+/**
+ * Retrive the next item from the \a iterator.
+ *
+ * \param iterator an iterator returned by box_index_iterator().
+ * \param[out] result a tuple or NULL if there is no more data.
+ * \retval -1 on error (check box_error_last() for details)
+ * \retval 0 on success. The end of data is not an error.
+ */
 API_EXPORT int
-box_iterator_next(box_iterator_t *itr, box_tuple_t **result);
+box_iterator_next(box_iterator_t *iterator, box_tuple_t **result);
 
+/**
+ * Destroy and deallocate iterator.
+ *
+ * \param iterator an interator returned by box_index_iterator()
+ */
 API_EXPORT void
-box_iterator_free(box_iterator_t *itr);
+box_iterator_free(box_iterator_t *iterator);
 
 /** \endcond public */
 
@@ -95,7 +123,6 @@ extern const char *iterator_type_strs[];
 struct iterator {
 	struct tuple *(*next)(struct iterator *);
 	void (*free)(struct iterator *);
-	void (*close)(struct iterator *);
 	/* optional parameters used in lua */
 	int sc_version;
 	uint32_t space_id;
@@ -168,26 +195,9 @@ class Index: public Object {
 	 */
 	Index(struct key_def *key_def);
 
-	/*
-	 * Pre-allocated iterator to speed up the main case of
-	 * box_process(). Should not be used elsewhere.
-	 */
-	mutable struct iterator *m_position;
 public:
 	virtual ~Index();
 
-	/**
-	 * Two-phase index creation: begin building, add tuples, finish.
-	 */
-	virtual void beginBuild();
-	/**
-	 * Optional hint, given to the index, about
-	 * the total size of the index. If given,
-	 * is given after beginBuild().
-	 */
-	virtual void reserve(uint32_t /* size_hint */);
-	virtual void buildNext(struct tuple *tuple);
-	virtual void endBuild();
 	virtual size_t size() const;
 	virtual struct tuple *min(const char *key, uint32_t part_count) const;
 	virtual struct tuple *max(const char *key, uint32_t part_count) const;
@@ -220,26 +230,13 @@ class Index: public Object {
 	 * for which createReadViewForIterator() was called.
 	 */
 	virtual void destroyReadViewForIterator(struct iterator *iterator);
-
-	inline struct iterator *position() const
-	{
-		if (m_position == NULL)
-			m_position = allocIterator();
-		return m_position;
-	}
 };
 
-struct IteratorGuard: public Object {
+struct IteratorGuard
+{
 	struct iterator *it;
-	IteratorGuard(struct iterator *it)
-		: it(it)
-	{}
-
-	~IteratorGuard()
-	{
-		if (it->close)
-			it->close(it);
-	}
+	IteratorGuard(struct iterator *it_arg) : it(it_arg) {}
+	~IteratorGuard() { it->free(it); }
 };
 
 /**
@@ -273,7 +270,6 @@ replace_check_dup(struct tuple *old_tuple, struct tuple *dup_tuple,
 	return 0;
 }
 
-
 /** Get index ordinal number in space. */
 static inline uint32_t
 index_id(const Index *index)
@@ -294,34 +290,116 @@ index_is_primary(const Index *index)
 	return index_id(index) == 0;
 }
 
-/** Build this index based on the contents of another index. */
-void
-index_build(Index *index, Index *pk);
-
 /** \cond public */
 
-API_EXPORT size_t
+/**
+ * Return the number of element in the index.
+ *
+ * \param space_id space identifier
+ * \param index_id index identifier
+ * \retval -1 on error (check box_error_last())
+ * \retval >= 0 otherwise
+ */
+API_EXPORT ssize_t
 box_index_len(uint32_t space_id, uint32_t index_id);
 
-API_EXPORT size_t
+/**
+ * Return the number of bytes used in memory by the index.
+ *
+ * \param space_id space identifier
+ * \param index_id index identifier
+ * \retval -1 on error (check box_error_last())
+ * \retval >= 0 otherwise
+ */
+API_EXPORT ssize_t
 box_index_bsize(uint32_t space_id, uint32_t index_id);
 
+/**
+ * Return a random tuple from the index (useful for statistical analysis).
+ *
+ * \param space_id space identifier
+ * \param index_id index identifier
+ * \param rnd random seed
+ * \param[out] result a tuple or NULL if index is empty
+ * \retval -1 on error (check box_error_last())
+ * \retval 0 on success
+ * \sa \code box.space[space_id].index[index_id]:random(rnd) \endcode
+ */
 API_EXPORT int
 box_index_random(uint32_t space_id, uint32_t index_id, uint32_t rnd,
 		box_tuple_t **result);
 
+/**
+ * Get a tuple from index by the key.
+ *
+ * Please note that this function works much more faster than
+ * box_select() or box_index_iterator() + box_iterator_next().
+ *
+ * \param space_id space identifier
+ * \param index_id index identifier
+ * \param key encoded key in MsgPack Array format ([part1, part2, ...]).
+ * \param key_end the end of encoded \a key
+ * \param[out] result a tuple or NULL if index is empty
+ * \retval -1 on error (check box_error_last())
+ * \retval 0 on success
+ * \pre key != NULL
+ * \sa \code box.space[space_id].index[index_id]:get(key) \endcode
+ */
 API_EXPORT int
 box_index_get(uint32_t space_id, uint32_t index_id, const char *key,
 	      const char *key_end, box_tuple_t **result);
 
+/**
+ * Return a first (minimal) tuple matched the provided key.
+ *
+ * \param space_id space identifier
+ * \param index_id index identifier
+ * \param key encoded key in MsgPack Array format ([part1, part2, ...]).
+ * If NULL then equvivalent to an empty array.
+ * \param key_end the end of encoded \a key.
+ * Must be NULL if \a key is NULL.
+ * \param[out] result a tuple or NULL if index is empty
+ * \retval -1 on error (check box_error_last())
+ * \retval 0 on success
+ * \sa \code box.space[space_id].index[index_id]:min(key) \endcode
+ */
 API_EXPORT int
 box_index_min(uint32_t space_id, uint32_t index_id, const char *key,
 	      const char *key_end, box_tuple_t **result);
 
+/**
+ * Return a last (maximal) tuple matched the provided key.
+ *
+ * \param space_id space identifier
+ * \param index_id index identifier
+ * \param key encoded key in MsgPack Array format ([part1, part2, ...]).
+ * If NULL then equvivalent to an empty array.
+ * \param key_end the end of encoded \a key.
+ * Must be NULL if \a key is NULL.
+ * \param[out] result a tuple or NULL if index is empty
+ * \retval -1 on error (check box_error_last())
+ * \retval 0 on success
+ * \sa \code box.space[space_id].index[index_id]:max(key) \endcode
+ */
 API_EXPORT int
 box_index_max(uint32_t space_id, uint32_t index_id, const char *key,
 	      const char *key_end, box_tuple_t **result);
 
+/**
+ * Count the number of tuple matched the provided key.
+ *
+ * \param space_id space identifier
+ * \param index_id index identifier
+ * \param type iterator type - enum \link iterator_type \endlink
+ * \param key encoded key in MsgPack Array format ([part1, part2, ...]).
+ * If NULL then equvivalent to an empty array.
+ * \param key_end the end of encoded \a key.
+ * Must be NULL if \a key is NULL.
+ * \retval -1 on error (check box_error_last())
+ * \retval >=0 on success
+ * \sa \code box.space[space_id].index[index_id]:count(key,
+ *     { iterator = type }) \endcode
+ */
 API_EXPORT ssize_t
 box_index_count(uint32_t space_id, uint32_t index_id, int type,
 		const char *key, const char *key_end);
diff --git a/src/box/iproto.cc b/src/box/iproto.cc
index 0cdb34e22f541bc2b14b96c9bebdca7e5027e1a3..be8026c38526d8fd0632e376becffe64549ca244 100644
--- a/src/box/iproto.cc
+++ b/src/box/iproto.cc
@@ -50,11 +50,12 @@
 #include "iproto_constants.h"
 #include "user_def.h"
 #include "authentication.h"
-#include "stat.h"
+#include "rmean.h"
 #include "lua/call.h"
 
 /* {{{ iproto_msg - declaration */
 
+
 /**
  * A single msg from io thread. All requests
  * from all connections are queued into a single queue
@@ -448,7 +449,7 @@ iproto_enqueue_batch(struct iproto_connection *con, struct ibuf *in)
 		 * in->rpos.
 		 */
 		if (msg->header.type >= IPROTO_SELECT &&
-		    msg->header.type <= IPROTO_EVAL) {
+		    msg->header.type <= IPROTO_UPSERT) {
 			/* Pre-parse request before putting it into the queue */
 			if (msg->header.bodycnt == 0) {
 				tnt_raise(ClientError, ER_INVALID_MSGPACK,
@@ -513,6 +514,9 @@ iproto_connection_on_input(ev_loop *loop, struct ev_io *watcher,
 			iproto_connection_close(con);
 			return;
 		}
+		/* Count statistics */
+		rmean_collect(rmean_net, RMEAN_NET_RECEIVED, nrd);
+
 		/* Update the read position and connection state. */
 		in->wpos += nrd;
 		con->parse_size += nrd;
@@ -564,6 +568,9 @@ iproto_flush(struct iobuf *iobuf, struct iproto_connection *con)
 	iov[iovcnt-1].iov_len = end->iov_len - begin->iov_len * (iovcnt == 1);
 
 	ssize_t nwr = sio_writev(fd, iov, iovcnt);
+
+	/* Count statistics */
+	rmean_collect(rmean_net, RMEAN_NET_SENT, nwr);
 	if (nwr > 0) {
 		if (begin->used + nwr == end->used) {
 			if (ibuf_used(&iobuf->in) == 0) {
@@ -626,16 +633,16 @@ tx_process_msg(struct cmsg *m)
 	try {
 		switch (msg->header.type) {
 		case IPROTO_SELECT:
-		case IPROTO_INSERT:
-		case IPROTO_REPLACE:
-		case IPROTO_UPDATE:
-		case IPROTO_DELETE:
-			assert(msg->request.type == msg->header.type);
+		{
 			struct iproto_port port;
 			iproto_port_init(&port, out, msg->header.sync);
-			try {
-				box_process(&msg->request, (struct port *) &port);
-			} catch (Exception *e) {
+			struct request *req = &msg->request;
+			int rc = box_select((struct port *) &port,
+					    req->space_id, req->index_id,
+					    req->iterator,
+					    req->offset, req->limit,
+					    req->key, req->key_end);
+			if (rc < 0) {
 				/*
 				 * This only works if there are no
 				 * yields between the moment the
@@ -645,17 +652,35 @@ tx_process_msg(struct cmsg *m)
 				 */
 				if (port.found)
 					obuf_rollback_to_svp(out, &port.svp);
-				throw;
+				throw (Exception *) box_error_last();
 			}
 			break;
+		}
+		case IPROTO_INSERT:
+		case IPROTO_REPLACE:
+		case IPROTO_UPDATE:
+		case IPROTO_DELETE:
+		case IPROTO_UPSERT:
+		{
+			assert(msg->request.type == msg->header.type);
+			struct tuple *tuple;
+			if (box_process1(&msg->request, &tuple) < 0)
+				throw (Exception *) box_error_last();
+			struct obuf_svp svp = iproto_prepare_select(out);
+			if (tuple)
+				tuple_to_obuf(tuple, out);
+			iproto_reply_select(out, &svp, msg->header.sync,
+					    tuple != 0);
+			break;
+		}
 		case IPROTO_CALL:
 			assert(msg->request.type == msg->header.type);
-			stat_collect(stat_base, msg->request.type, 1);
+			rmean_collect(rmean_box, msg->request.type, 1);
 			box_lua_call(&msg->request, out);
 			break;
 		case IPROTO_EVAL:
 			assert(msg->request.type == msg->header.type);
-			stat_collect(stat_base, msg->request.type, 1);
+			rmean_collect(rmean_box, msg->request.type, 1);
 			box_lua_eval(&msg->request, out);
 			break;
 		case IPROTO_AUTH:
@@ -771,8 +796,11 @@ net_send_greeting(struct cmsg *m)
 	if (msg->close_connection) {
 		struct obuf *out = &msg->iobuf->out;
 		try {
-			sio_writev(con->output.fd, out->iov,
-				   obuf_iovcnt(out));
+			int64_t nwr = sio_writev(con->output.fd, out->iov,
+						 obuf_iovcnt(out));
+
+			/* Count statistics */
+			rmean_collect(rmean_net, RMEAN_NET_SENT, nwr);
 		} catch (Exception *e) {
 			e->log();
 		}
@@ -845,13 +873,27 @@ net_cord_f(va_list /* ap */)
 	evio_service_init(loop(), &binary, "binary",
 			  iproto_on_accept, NULL);
 
+
+	/* Init statistics counter */
+	rmean_net = rmean_new(rmean_net_strings, RMEAN_NET_LAST);
+
+	if (rmean_net == NULL)
+		tnt_raise(OutOfMemory,
+			  sizeof(*rmean_net) +
+			  RMEAN_NET_LAST * sizeof(stats),
+			  "rmean", "struct rmean");
+
+
 	cbus_join(&net_tx_bus, &net_pipe);
+
 	/*
 	 * Nothing to do in the fiber so far, the service
 	 * will take care of creating events for incoming
 	 * connections.
 	 */
 	fiber_yield();
+
+	rmean_delete(rmean_net);
 }
 
 /** Initialize the iproto subsystem and start network io thread */
@@ -872,6 +914,7 @@ iproto_init()
 	if (cord_costart(&net_cord, "iproto", net_cord_f, NULL))
 		panic("failed to initialize iproto thread");
 
+
 	cbus_join(&net_tx_bus, &tx_pipe);
 }
 
diff --git a/src/box/iproto_port.h b/src/box/iproto_port.h
index 254e64528e8a069d0bffb747857ca3b44a93d2fd..f1b62b7bd82c9e4e0f19188fe489c2c6446e69d1 100644
--- a/src/box/iproto_port.h
+++ b/src/box/iproto_port.h
@@ -57,7 +57,7 @@
  */
 struct iproto_port
 {
-	struct port_vtab *vtab;
+	struct port base;
 	/** Output buffer. */
 	struct obuf *buf;
 	/** Reply header. */
@@ -74,7 +74,7 @@ static inline void
 iproto_port_init(struct iproto_port *port, struct obuf *buf,
 		 uint64_t sync)
 {
-	port->vtab = &iproto_port_vtab;
+	port->base.vtab = &iproto_port_vtab;
 	port->buf = buf;
 	port->sync = sync;
 	port->found = 0;
diff --git a/src/box/key_def.cc b/src/box/key_def.cc
index eb1343e3348e337f3b9a7bffd67eb7f1b2ca87e8..c898674434005151c77de2c412ee3f642c544352 100644
--- a/src/box/key_def.cc
+++ b/src/box/key_def.cc
@@ -37,6 +37,8 @@
 const char *field_type_strs[] = {"UNKNOWN", "NUM", "STR", "ARRAY", "NUMBER", ""};
 STRS(index_type, ENUM_INDEX_TYPE);
 
+const char *rtree_index_distance_type_strs[] = { "EUCLID", "MANHATTAN" };
+
 const char *func_language_strs[] = {"LUA", "C"};
 
 const uint32_t key_mp_type[] = {
@@ -47,6 +49,10 @@ const uint32_t key_mp_type[] = {
 	/* [NUMBER]  =  */  (1U << MP_UINT) | (1U << MP_INT) | (1U << MP_FLOAT) | (1U << MP_DOUBLE),
 };
 
+const struct key_opts key_opts_default = {
+	true, 0, RTREE_INDEX_DISTANCE_TYPE_EUCLID
+};
+
 enum schema_object_type
 schema_object_type(const char *name)
 {
diff --git a/src/box/key_def.h b/src/box/key_def.h
index f8db51cad1f4461054f22229cbda9323b8deb59c..c9e6e74976b4dcec197c9ac8420a44d7ddc83910 100644
--- a/src/box/key_def.h
+++ b/src/box/key_def.h
@@ -99,6 +99,15 @@ field_type_maxlen(enum field_type type)
 ENUM(index_type, ENUM_INDEX_TYPE);
 extern const char *index_type_strs[];
 
+enum rtree_index_distance_type {
+	 /* Euclid distance, sqrt(dx*dx + dy*dy) */
+	RTREE_INDEX_DISTANCE_TYPE_EUCLID,
+	/* Manhattan distance, fabs(dx) + fabs(dy) */
+	RTREE_INDEX_DISTANCE_TYPE_MANHATTAN,
+	rtree_index_distance_type_MAX
+};
+extern const char *rtree_index_distance_type_strs[];
+
 /** Descriptor of a single part in a multipart key. */
 struct key_part {
 	uint32_t fieldno;
@@ -116,22 +125,23 @@ struct key_opts {
 	 * RTREE index dimension.
 	 */
 	uint32_t dimension;
+	/**
+	 * RTREE distance type.
+	 */
+	enum rtree_index_distance_type distance;
 };
 
-static inline void
-key_opts_create(struct key_opts *opts)
-{
-	opts->is_unique = true;
-	opts->dimension = 2;
-}
+extern const struct key_opts key_opts_default;
 
 static inline int
 key_opts_cmp(const struct key_opts *o1, const struct key_opts *o2)
 {
 	if (o1->is_unique != o2->is_unique)
 		return o1->is_unique < o2->is_unique ? -1 : 1;
-	if (o2->dimension != o1->dimension)
-		return o1->dimension < o2-> dimension ? -1 : 1;
+	if (o1->dimension != o2->dimension)
+		return o1->dimension < o2->dimension ? -1 : 1;
+	if (o1->distance != o2->distance)
+		return o1->distance < o2->distance ? -1 : 1;
 	return 0;
 }
 
diff --git a/src/box/lua/call.cc b/src/box/lua/call.cc
index 5c56e67ddde0a7aa53154378dfabc54554cb48b5..4d933247e20d690833af0ecf842aa5be0ef46fcd 100644
--- a/src/box/lua/call.cc
+++ b/src/box/lua/call.cc
@@ -38,6 +38,7 @@
 #include "box/lua/stat.h"
 #include "box/lua/info.h"
 #include "box/lua/session.h"
+#include "box/lua/net_box.h"
 #include "box/tuple.h"
 
 #include "lua/utils.h"
@@ -62,13 +63,15 @@
 extern char session_lua[],
 	schema_lua[],
 	load_cfg_lua[],
-	snapshot_daemon_lua[];
+	snapshot_daemon_lua[],
+	net_box_lua[];
 
 static const char *lua_sources[] = {
 	session_lua,
 	schema_lua,
 	snapshot_daemon_lua,
 	load_cfg_lua,
+	net_box_lua,
 	NULL
 };
 
@@ -150,44 +153,6 @@ port_lua_table_create(struct port_lua *port, struct lua_State *L)
 
 /* }}} */
 
-/**
- * The main extension provided to Lua by Tarantool/Box --
- * ability to call INSERT/UPDATE/SELECT/DELETE from within
- * a Lua procedure.
- *
- * This is a low-level API, and it expects
- * all arguments to be packed in accordance
- * with the binary protocol format (iproto
- * header excluded).
- *
- * Signature:
- * box.process(op_code, request)
- */
-static int
-lbox_process(lua_State *L)
-{
-	uint32_t op = lua_tointeger(L, 1); /* Get the first arg. */
-	size_t sz;
-	const char *req = luaL_checklstring(L, 2, &sz); /* Second arg. */
-	if (op == IPROTO_CALL) {
-		/*
-		 * We should not be doing a CALL from within a CALL.
-		 * To invoke one stored procedure from another, one must
-		 * do it in Lua directly. This deals with
-		 * infinite recursion, stack overflow and such.
-		 */
-		return luaL_error(L, "box.process(CALL, ...) is not allowed");
-	}
-	/* Capture all output into a Lua table. */
-	struct port_lua port_lua;
-	struct request request;
-	request_create(&request, op);
-	request_decode(&request, req, sz);
-	port_lua_table_create(&port_lua, L);
-	box_process(&request, (struct port *) &port_lua);
-	return 1;
-}
-
 static int
 lbox_select(lua_State *L)
 {
@@ -727,7 +692,6 @@ static const struct luaL_reg boxlib[] = {
 };
 
 static const struct luaL_reg boxlib_internal[] = {
-	{"process", lbox_process},
 	{"call_loadproc",  lbox_call_loadproc},
 	{"select", lbox_select},
 	{"insert", lbox_insert},
@@ -746,6 +710,8 @@ box_lua_init(struct lua_State *L)
 	lua_pop(L, 1);
 	luaL_register(L, "box.internal", boxlib_internal);
 	lua_pop(L, 1);
+	luaopen_net_box(L);
+	lua_pop(L, 1);
 
 #if 0
 	/* Get CTypeID for `struct port *' */
@@ -760,6 +726,7 @@ box_lua_init(struct lua_State *L)
 	box_lua_index_init(L);
 	box_lua_space_init(L);
 	box_lua_info_init(L);
+	box_lua_sophia_init(L);
 	box_lua_stat_init(L);
 	box_lua_session_init(L);
 
diff --git a/src/box/lua/info.cc b/src/box/lua/info.cc
index 28372d03815c264540c72b668e73c1d533f45673..d8230811b7b0a8e6019aa588982d0754c6345d5d 100644
--- a/src/box/lua/info.cc
+++ b/src/box/lua/info.cc
@@ -148,30 +148,6 @@ lbox_info_pid(struct lua_State *L)
 	return 1;
 }
 
-#if 0
-void sophia_info(void (*)(const char*, const char*, void*), void*);
-
-static void
-lbox_info_sophia_cb(const char *key, const char *value, void *arg)
-{
-	struct lua_State *L;
-	L = (struct lua_State*)arg;
-	if (value == NULL)
-		return;
-	lua_pushstring(L, key);
-	lua_pushstring(L, value);
-	lua_settable(L, -3);
-}
-
-static int
-lbox_info_sophia(struct lua_State *L)
-{
-	lua_newtable(L);
-	sophia_info(lbox_info_sophia_cb, (void*)L);
-	return 1;
-}
-#endif
-
 static const struct luaL_reg
 lbox_info_dynamic_meta [] =
 {
@@ -181,9 +157,6 @@ lbox_info_dynamic_meta [] =
 	{"status", lbox_info_status},
 	{"uptime", lbox_info_uptime},
 	{"pid", lbox_info_pid},
-#if 0
-	{"sophia", lbox_info_sophia},
-#endif
 	{NULL, NULL}
 };
 
@@ -264,3 +237,53 @@ box_lua_info_init(struct lua_State *L)
 
 	lua_pop(L, 1); /* info module */
 }
+
+void sophia_info(void (*)(const char*, const char*, int, void*), void*);
+
+static void
+lbox_sophia_cb(const char *key, const char *value, int /* pos */, void *arg)
+{
+	struct lua_State *L;
+	L = (struct lua_State*)arg;
+	if (value == NULL)
+		return;
+	lua_pushstring(L, key);
+	lua_pushstring(L, value);
+	lua_settable(L, -3);
+}
+
+/**
+ * When user invokes box.sophia(), return a table of key/value
+ * pairs containing the current info.
+ */
+static int
+lbox_sophia_call(struct lua_State *L)
+{
+	lua_newtable(L);
+	sophia_info(lbox_sophia_cb, (void*)L);
+	return 1;
+}
+
+/** Initialize box.sophia package. */
+void
+box_lua_sophia_init(struct lua_State *L)
+{
+	static const struct luaL_reg sophialib [] = {
+		{NULL, NULL}
+	};
+
+	luaL_register_module(L, "box.sophia", sophialib);
+
+	lua_newtable(L);
+
+	lua_pushstring(L, "__call");
+	lua_pushcfunction(L, lbox_sophia_call);
+	lua_settable(L, -3);
+
+	lua_pushstring(L, "__serialize");
+	lua_pushcfunction(L, lbox_sophia_call);
+	lua_settable(L, -3);
+
+	lua_setmetatable(L, -2);
+	lua_pop(L, 1);
+}
diff --git a/src/box/lua/info.h b/src/box/lua/info.h
index a6093f47a58312db7808958bc9540b75747495de..ab4695e81e0fa10067d41a6c9cf8d25f1e6399d1 100644
--- a/src/box/lua/info.h
+++ b/src/box/lua/info.h
@@ -34,5 +34,6 @@
 
 struct lua_State;
 void box_lua_info_init(struct lua_State *L);
+void box_lua_sophia_init(struct lua_State *L);
 
 #endif /* INCLUDES_TARANTOOL_LUA_INFO_H */
diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua
index 3bec896c17d782171c90df155680ca133f928b81..7561d1028976234737e9df8bc2a409bd5ecb0820 100644
--- a/src/box/lua/load_cfg.lua
+++ b/src/box/lua/load_cfg.lua
@@ -21,10 +21,11 @@ local json = require('json')
 -- see default_cfg below
 local default_sophia_cfg = {
     memory_limit = 0,
-    threads      = 5,
-    node_size    = 134217728,
-    page_size    = 131072,
-    compression  = "none"
+    threads         = 5,
+    node_size       = 134217728,
+    page_size       = 131072,
+    compression     = "none",
+    compression_key = 0
 }
 
 -- all available options
@@ -65,11 +66,12 @@ local default_cfg = {
 
 -- see template_cfg below
 local sophia_template_cfg = {
-    memory_limit = 'number',
-    threads      = 'number',
-    node_size    = 'number',
-    page_size    = 'number',
-    compression  = 'string'
+    memory_limit    = 'number',
+    threads         = 'number',
+    node_size       = 'number',
+    page_size       = 'number',
+    compression     = 'string',
+    compression_key = 'number'
 }
 
 -- types of available options
diff --git a/src/lua/net_box.cc b/src/box/lua/net_box.cc
similarity index 89%
rename from src/lua/net_box.cc
rename to src/box/lua/net_box.cc
index 9e6fc68e0c9e5702205a096818d73f5c05dc0f46..db0ef3e376ef85509a22fae0fb1f07652163adca 100644
--- a/src/lua/net_box.cc
+++ b/src/box/lua/net_box.cc
@@ -29,16 +29,15 @@
  * SUCH DAMAGE.
  */
 
-#include "lua/net_box.h"
-#include "lua/msgpack.h"
+#include "net_box.h"
 
 #include <lib/small/ibuf.h>
 #include "scramble.h"
 
-/* TODO: net.box depends on src/box/ */
 #include "box/iproto_constants.h"
 #include "box/lua/tuple.h" /* luamp_convert_tuple() / luamp_convert_key() */
 
+#include "lua/msgpack.h"
 #include <msgpuck/msgpuck.h> /* mp_store_u32() */
 #include "third_party/base64.h"
 
@@ -358,6 +357,51 @@ netbox_encode_update(lua_State *L)
 	return 0;
 }
 
+static int
+netbox_encode_upsert(lua_State *L)
+{
+	if (lua_gettop(L) < 7)
+		return luaL_error(L, "Usage: netbox.encode_update(ibuf, sync, "
+			"space_id, index_id, key, ops, tuple)");
+
+	struct mpstream stream;
+	size_t svp = netbox_prepare_request(L, &stream, IPROTO_UPSERT);
+
+	luamp_encode_map(cfg, &stream, 6);
+
+	/* encode space_id */
+	uint32_t space_id = lua_tointeger(L, 3);
+	luamp_encode_uint(cfg, &stream, IPROTO_SPACE_ID);
+	luamp_encode_uint(cfg, &stream, space_id);
+
+	/* encode index_id */
+	uint32_t index_id = lua_tointeger(L, 4);
+	luamp_encode_uint(cfg, &stream, IPROTO_INDEX_ID);
+	luamp_encode_uint(cfg, &stream, index_id);
+
+	/* encode index_id */
+	luamp_encode_uint(cfg, &stream, IPROTO_INDEX_BASE);
+	luamp_encode_uint(cfg, &stream, 1);
+
+	/* encode in reverse order for speedup - see luamp_encode() code */
+	/* encode tuple */
+	luamp_encode_uint(cfg, &stream, IPROTO_TUPLE);
+	luamp_encode_tuple(L, cfg, &stream, 7);
+	lua_pop(L, 1); /* tuple */
+
+	/* encode ops */
+	luamp_encode_uint(cfg, &stream, IPROTO_OPS);
+	luamp_encode_tuple(L, cfg, &stream, 6);
+	lua_pop(L, 1); /* ops */
+
+	/* encode key */
+	luamp_encode_uint(cfg, &stream, IPROTO_KEY);
+	luamp_convert_key(L, cfg, &stream, 5);
+
+	netbox_encode_request(&stream, svp);
+	return 0;
+}
+
 int
 luaopen_net_box(struct lua_State *L)
 {
@@ -370,6 +414,7 @@ luaopen_net_box(struct lua_State *L)
 		{ "encode_replace", netbox_encode_replace },
 		{ "encode_delete",  netbox_encode_delete },
 		{ "encode_update",  netbox_encode_update },
+		{ "encode_upsert",  netbox_encode_upsert },
 		{ "encode_auth",    netbox_encode_auth },
 		{ NULL, NULL}
 	};
diff --git a/src/lua/net_box.h b/src/box/lua/net_box.h
similarity index 100%
rename from src/lua/net_box.h
rename to src/box/lua/net_box.h
diff --git a/src/lua/net_box.lua b/src/box/lua/net_box.lua
similarity index 97%
rename from src/lua/net_box.lua
rename to src/box/lua/net_box.lua
index 76cbfa1371ab294b1606c8aadf243b8b86259b26..ca38ec33166026d0f19fcf48df1b9b71a988302b 100644
--- a/src/lua/net_box.lua
+++ b/src/box/lua/net_box.lua
@@ -21,6 +21,7 @@ local DELETE            = 5
 local CALL              = 6
 local AUTH              = 7
 local EVAL              = 8
+local UPSERT            = 9
 local PING              = 64
 local ERROR_TYPE        = 65536
 
@@ -38,6 +39,7 @@ local TUPLE             = 0x21
 local FUNCTION_NAME     = 0x22
 local USER              = 0x23
 local EXPR              = 0x27
+local OPS               = 0x28
 local DATA              = 0x30
 local ERROR             = 0x31
 local GREETING_SIZE     = 128
@@ -92,6 +94,7 @@ local requests = {
     [REPLACE] = internal.encode_replace;
     [DELETE] = internal.encode_delete;
     [UPDATE]  = internal.encode_update;
+    [UPSERT]  = internal.encode_upsert;
     [SELECT]  = function(wbuf, sync, spaceno, indexno, key, opts)
         if opts == nil then
             opts = {}
@@ -158,6 +161,11 @@ local function space_metatable(self)
                 return self:_update(space.id, key, oplist, 0)
             end,
 
+            upsert = function(space, key, oplist, tuple)
+                check_if_space(space)
+                return self:_upsert(space.id, key, oplist, tuple, 0)
+            end,
+
             get = function(space, key)
                 check_if_space(space)
                 local res = self:_select(space.id, 0, key,
@@ -240,6 +248,11 @@ local function index_metatable(self)
                 return self:_update(idx.space.id, key, oplist, idx.id)
             end,
 
+            upsert = function(idx, key, oplist, tuple)
+                check_if_index(idx)
+                return self:_upsert(idx.space.id, key, oplist, tuple, idx.id)
+            end,
+
         }
     }
 end
@@ -966,7 +979,13 @@ local remote_methods = {
     _update = function(self, spaceno, key, oplist, index_id)
         local res = self:_request(UPDATE, true, spaceno, index_id, key, oplist)
         return one_tuple(res.body[DATA])
-    end
+    end,
+
+    _upsert = function(self, spaceno, key, oplist, tuple, index_id)
+        local res = self:_request(UPSERT, true, spaceno,
+                                  index_id, key, oplist, tuple)
+        return one_tuple(res.body[DATA])
+    end,
 }
 
 setmetatable(remote, { __index = remote_methods })
@@ -1057,4 +1076,4 @@ setmetatable(remote.self, {
     end
 })
 
-return remote
+package.loaded['net.box'] = remote
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index e1772af58a024654196c34efcd708ce91aeac97a..308d1842d41a65cb8a842d85be82e77cc6fed368 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -289,7 +289,7 @@ end
 
 -- space format - the metadata about space fields
 function box.schema.space.format(id, format)
-    _space = box.space._space
+    local _space = box.space._space
     check_param(id, 'id', 'number')
     check_param(format, 'format', 'table')
     if format == nil then
@@ -379,6 +379,7 @@ box.schema.index.create = function(space_id, name, options)
         id = 'number',
         if_not_exists = 'boolean',
         dimension = 'number',
+        distance = 'string',
     }
     local options_defaults = {
         type = 'tree',
@@ -417,7 +418,8 @@ box.schema.index.create = function(space_id, name, options)
     for i = 1, #options.parts, 2 do
         table.insert(parts, {options.parts[i], options.parts[i + 1]})
     end
-    local key_opts = { dimension = options.dimension, unique = options.unique }
+    local key_opts = { dimension = options.dimension,
+        unique = options.unique, distance = options.distance }
     _index:insert{space_id, iid, name, options.type, key_opts, parts}
     return box.space[space_id].index[name]
 end
@@ -443,6 +445,9 @@ box.schema.index.alter = function(space_id, index_id, options)
     if box.space[space_id] == nil then
         box.error(box.error.NO_SUCH_SPACE, '#'..tostring(space_id))
     end
+	if box.space[space_id].engine == 'sophia' then
+		box.error(box.error.SOPHIA, 'alter is not supported for a Sophia index')
+	end
     if box.space[space_id].index[index_id] == nil then
         box.error(box.error.NO_SUCH_INDEX, index_id, box.space[space_id].name)
     end
@@ -457,6 +462,7 @@ box.schema.index.alter = function(space_id, index_id, options)
         parts = 'table',
         unique = 'boolean',
         dimension = 'number',
+        distance = 'string',
     }
     check_param_table(options, options_template)
 
@@ -523,6 +529,9 @@ box.schema.index.alter = function(space_id, index_id, options)
     if options.dimension ~= nil then
         key_opts.dimension = options.dimension
     end
+    if options.distance ~= nil then
+        key_opts.distance = options.distance
+    end
     if options.parts ~= nil then
         check_index_parts(options.parts)
         options.parts = update_index_parts(options.parts)
diff --git a/src/box/lua/stat.cc b/src/box/lua/stat.cc
index 00b85a415dd04de56481664b956c2980cc8e4e28..efdc1d3121de07c0fda45aa7239361c53ad7187b 100644
--- a/src/box/lua/stat.cc
+++ b/src/box/lua/stat.cc
@@ -32,7 +32,10 @@
 #include "stat.h"
 
 #include <string.h>
-#include <stat.h>
+#include <rmean.h>
+#include <box/request.h>
+#include <cbus.h>
+#include <box/error.h>
 
 extern "C" {
 #include <lua.h>
@@ -90,14 +93,33 @@ static int
 lbox_stat_index(struct lua_State *L)
 {
 	luaL_checkstring(L, -1);
-	return stat_foreach(seek_stat_item, L);
+	int res = rmean_foreach(rmean_box, seek_stat_item, L);
+	if (res)
+		return res;
+	return rmean_foreach(rmean_error, seek_stat_item, L);
 }
 
 static int
 lbox_stat_call(struct lua_State *L)
 {
 	lua_newtable(L);
-	stat_foreach(set_stat_item, L);
+	rmean_foreach(rmean_box, set_stat_item, L);
+	rmean_foreach(rmean_error, set_stat_item, L);
+	return 1;
+}
+
+static int
+lbox_stat_net_index(struct lua_State *L)
+{
+	luaL_checkstring(L, -1);
+	return rmean_foreach(rmean_net, seek_stat_item, L);
+}
+
+static int
+lbox_stat_net_call(struct lua_State *L)
+{
+	lua_newtable(L);
+	rmean_foreach(rmean_net, set_stat_item, L);
 	return 1;
 }
 
@@ -107,7 +129,13 @@ static const struct luaL_reg lbox_stat_meta [] = {
 	{NULL, NULL}
 };
 
-/** Initialize bos.stat package. */
+static const struct luaL_reg lbox_stat_net_meta [] = {
+	{"__index", lbox_stat_net_index},
+	{"__call",  lbox_stat_net_call},
+	{NULL, NULL}
+};
+
+/** Initialize box.stat package. */
 void
 box_lua_stat_init(struct lua_State *L)
 {
@@ -120,7 +148,14 @@ box_lua_stat_init(struct lua_State *L)
 	lua_newtable(L);
 	luaL_register(L, NULL, lbox_stat_meta);
 	lua_setmetatable(L, -2);
-
 	lua_pop(L, 1); /* stat module */
+
+
+	luaL_register_module(L, "box.stat.net", statlib);
+
+	lua_newtable(L);
+	luaL_register(L, NULL, lbox_stat_net_meta);
+	lua_setmetatable(L, -2);
+	lua_pop(L, 1); /* stat net module */
 }
 
diff --git a/src/box/memtx_bitset.cc b/src/box/memtx_bitset.cc
index ed1caa67b3b3467e6b29002406d5802f7fa3059f..0de5b4c95f4ab3a53f2badddf1be62d33bd05808 100644
--- a/src/box/memtx_bitset.cc
+++ b/src/box/memtx_bitset.cc
@@ -181,7 +181,7 @@ bitset_index_iterator_next(struct iterator *iterator)
 }
 
 MemtxBitset::MemtxBitset(struct key_def *key_def)
-	: Index(key_def)
+	: MemtxIndex(key_def)
 {
 	assert(!this->key_def->opts.is_unique);
 
@@ -455,5 +455,5 @@ MemtxBitset::count(enum iterator_type type, const char *key,
 	}
 
 	/* Call generic method */
-	return Index::count(type, key, part_count);
+	return MemtxIndex::count(type, key, part_count);
 }
diff --git a/src/box/memtx_bitset.h b/src/box/memtx_bitset.h
index 4a185d14221eff859c0a419aef8ea57d25008dc3..528c2fa91a21809bef6fd30b57a0caa152e20b4c 100644
--- a/src/box/memtx_bitset.h
+++ b/src/box/memtx_bitset.h
@@ -35,7 +35,7 @@
  * @brief Index API wrapper for bitset_index
  * @see bitset/index.h
  */
-#include "index.h"
+#include "memtx_index.h"
 #include "bitset/index.h"
 
 #ifndef OLD_GOOD_BITSET
@@ -43,7 +43,7 @@ struct matras;
 struct mh_bitset_index_t;
 #endif /*#ifndef OLD_GOOD_BITSET*/
 
-class MemtxBitset: public Index {
+class MemtxBitset: public MemtxIndex {
 public:
 	MemtxBitset(struct key_def *key_def);
 	virtual ~MemtxBitset();
diff --git a/src/box/memtx_engine.cc b/src/box/memtx_engine.cc
index c3f8fdb793314b9755fd1af89735fb8658f85fe3..f0dee94b03e91f585782a34a0a31b74ac9d56e84 100644
--- a/src/box/memtx_engine.cc
+++ b/src/box/memtx_engine.cc
@@ -37,6 +37,8 @@
 #include "memtx_rtree.h"
 #include "memtx_bitset.h"
 #include "space.h"
+#include "msgpuck/msgpuck.h"
+#include "salad/rlist.h"
 #include "request.h"
 #include "box.h"
 #include "iproto_constants.h"
@@ -44,6 +46,7 @@
 #include "recovery.h"
 #include "relay.h"
 #include "schema.h"
+#include "port.h"
 #include "main.h"
 #include "coeio_file.h"
 #include "coeio.h"
@@ -93,9 +96,9 @@ memtx_replace_no_keys(struct txn * /* txn */, struct space *space,
 		      struct tuple * /* new_tuple */,
 		      enum dup_replace_mode /* mode */)
 {
-       Index *index = index_find(space, 0);
-       assert(index == NULL); /* not reached. */
-       (void) index;
+	Index *index = index_find(space, 0);
+	assert(index == NULL); /* not reached. */
+	(void) index;
 }
 
 struct MemtxSpace: public Handler {
@@ -109,8 +112,280 @@ struct MemtxSpace: public Handler {
 		/* do nothing */
 		/* engine->close(this); */
 	}
+	virtual struct tuple *
+	executeReplace(struct txn *txn, struct space *space,
+		       struct request *request);
+	virtual struct tuple *
+	executeDelete(struct txn *txn, struct space *space,
+		      struct request *request);
+	virtual struct tuple *
+	executeUpdate(struct txn *txn, struct space *space,
+		      struct request *request);
+	virtual void
+	executeUpsert(struct txn *txn, struct space *space,
+		      struct request *request);
+	virtual void
+	executeSelect(struct txn *, struct space *space,
+		      uint32_t index_id, uint32_t iterator,
+		      uint32_t offset, uint32_t limit,
+		      const char *key, const char * /* key_end */,
+		      struct port *port);
+	virtual void onAlter(Handler *old);
+public:
+	/**
+	 * @brief A single method to handle REPLACE, DELETE and UPDATE.
+	 *
+	 * @param sp space
+	 * @param old_tuple the tuple that should be removed (can be NULL)
+	 * @param new_tuple the tuple that should be inserted (can be NULL)
+	 * @param mode      dup_replace_mode, used only if new_tuple is not
+	 *                  NULL and old_tuple is NULL, and only for the
+	 *                  primary key.
+	 *
+	 * For DELETE, new_tuple must be NULL. old_tuple must be
+	 * previously found in the primary key.
+	 *
+	 * For REPLACE, old_tuple must be NULL. The additional
+	 * argument dup_replace_mode further defines how REPLACE
+	 * should proceed.
+	 *
+	 * For UPDATE, both old_tuple and new_tuple must be given,
+	 * where old_tuple must be previously found in the primary key.
+	 *
+	 * Let's consider these three cases in detail:
+	 *
+	 * 1. DELETE, old_tuple is not NULL, new_tuple is NULL
+	 *    The effect is that old_tuple is removed from all
+	 *    indexes. dup_replace_mode is ignored.
+	 *
+	 * 2. REPLACE, old_tuple is NULL, new_tuple is not NULL,
+	 *    has one simple sub-case and two with further
+	 *    ramifications:
+	 *
+	 *	A. dup_replace_mode is DUP_INSERT. Attempts to insert the
+	 *	new tuple into all indexes. If *any* of the unique indexes
+	 *	has a duplicate key, deletion is aborted, all of its
+	 *	effects are removed, and an error is thrown.
+	 *
+	 *	B. dup_replace_mode is DUP_REPLACE. It means an existing
+	 *	tuple has to be replaced with the new one. To do it, tries
+	 *	to find a tuple with a duplicate key in the primary index.
+	 *	If the tuple is not found, throws an error. Otherwise,
+	 *	replaces the old tuple with a new one in the primary key.
+	 *	Continues on to secondary keys, but if there is any
+	 *	secondary key, which has a duplicate tuple, but one which
+	 *	is different from the duplicate found in the primary key,
+	 *	aborts, puts everything back, throws an exception.
+	 *
+	 *	For example, if there is a space with 3 unique keys and
+	 *	two tuples { 1, 2, 3 } and { 3, 1, 2 }:
+	 *
+	 *	This REPLACE/DUP_REPLACE is OK: { 1, 5, 5 }
+	 *	This REPLACE/DUP_REPLACE is not OK: { 2, 2, 2 } (there
+	 *	is no tuple with key '2' in the primary key)
+	 *	This REPLACE/DUP_REPLACE is not OK: { 1, 1, 1 } (there
+	 *	is a conflicting tuple in the secondary unique key).
+	 *
+	 *	C. dup_replace_mode is DUP_REPLACE_OR_INSERT. If
+	 *	there is a duplicate tuple in the primary key, behaves the
+	 *	same way as DUP_REPLACE, otherwise behaves the same way as
+	 *	DUP_INSERT.
+	 *
+	 * 3. UPDATE has to delete the old tuple and insert a new one.
+	 *    dup_replace_mode is ignored.
+	 *    Note that old_tuple primary key doesn't have to match
+	 *    new_tuple primary key, thus a duplicate can be found.
+	 *    For this reason, and since there can be duplicates in
+	 *    other indexes, UPDATE is the same as DELETE +
+	 *    REPLACE/DUP_INSERT.
+	 *
+	 * @return old_tuple. DELETE, UPDATE and REPLACE/DUP_REPLACE
+	 * always produce an old tuple. REPLACE/DUP_INSERT always returns
+	 * NULL. REPLACE/DUP_REPLACE_OR_INSERT may or may not find
+	 * a duplicate.
+	 *
+	 * The method is all-or-nothing in all cases. Changes are either
+	 * applied to all indexes, or nothing applied at all.
+	 *
+	 * Note, that even in case of REPLACE, dup_replace_mode only
+	 * affects the primary key, for secondary keys it's always
+	 * DUP_INSERT.
+	 *
+	 * The call never removes more than one tuple: if
+	 * old_tuple is given, dup_replace_mode is ignored.
+	 * Otherwise, it's taken into account only for the
+	 * primary key.
+	 */
+	engine_replace_f replace;
 };
 
+static inline enum dup_replace_mode
+dup_replace_mode(uint32_t op)
+{
+	return op == IPROTO_INSERT ? DUP_INSERT : DUP_REPLACE_OR_INSERT;
+}
+
+struct tuple *
+MemtxSpace::executeReplace(struct txn *txn, struct space *space,
+			   struct request *request)
+{
+	struct tuple *new_tuple = tuple_new(space->format, request->tuple,
+					    request->tuple_end);
+	TupleGuard guard(new_tuple);
+	space_validate_tuple(space, new_tuple);
+	enum dup_replace_mode mode = dup_replace_mode(request->type);
+	this->replace(txn, space, NULL, new_tuple, mode);
+	txn_commit_stmt(txn);
+	/*
+	 * Adding result to port must be after possible WAL write.
+	 * The reason is that any yield between port_add_tuple and port_eof
+	 * calls could lead to sending not finished response to iproto socket.
+	 */
+	tuple_bless(new_tuple);
+	return new_tuple;
+}
+
+struct tuple *
+MemtxSpace::executeDelete(struct txn *txn, struct space *space,
+			  struct request *request)
+{
+	/* Try to find the tuple by unique key. */
+	Index *pk = index_find_unique(space, request->index_id);
+	const char *key = request->key;
+	uint32_t part_count = mp_decode_array(&key);
+	primary_key_validate(pk->key_def, key, part_count);
+	struct tuple *old_tuple = pk->findByKey(key, part_count);
+	if (old_tuple == NULL) {
+		txn_commit_stmt(txn);
+		return NULL;
+	}
+	TupleGuard old_guard(old_tuple);
+	this->replace(txn, space, old_tuple, NULL,
+				DUP_REPLACE_OR_INSERT);
+	txn_commit_stmt(txn);
+	/*
+	 * Adding result to port must be after possible WAL write.
+	 * The reason is that any yield between port_add_tuple and port_eof
+	 * calls could lead to sending not finished response to iproto socket.
+	 */
+	tuple_bless(old_tuple);
+	return old_tuple;
+}
+
+struct tuple *
+MemtxSpace::executeUpdate(struct txn *txn, struct space *space,
+			  struct request *request)
+{
+	/* Try to find the tuple by unique key. */
+	Index *pk = index_find_unique(space, request->index_id);
+	const char *key = request->key;
+	uint32_t part_count = mp_decode_array(&key);
+	primary_key_validate(pk->key_def, key, part_count);
+	struct tuple *old_tuple = pk->findByKey(key, part_count);
+
+	if (old_tuple == NULL) {
+		txn_commit_stmt(txn);
+		return NULL;
+	}
+	TupleGuard old_guard(old_tuple);
+
+	/* Update the tuple; legacy, request ops are in request->tuple */
+	struct tuple *new_tuple = tuple_update(space->format,
+					       region_alloc_cb,
+					       &fiber()->gc,
+					       old_tuple, request->tuple,
+					       request->tuple_end,
+					       request->index_base);
+	TupleGuard guard(new_tuple);
+	space_validate_tuple(space, new_tuple);
+	this->replace(txn, space, old_tuple, new_tuple, DUP_REPLACE);
+	txn_commit_stmt(txn);
+	/*
+	 * Adding result to port must be after possible WAL write.
+	 * The reason is that any yield between port_add_tuple and port_eof
+	 * calls could lead to sending not finished response to iproto socket.
+	 */
+	tuple_bless(new_tuple);
+	return new_tuple;
+}
+
+void
+MemtxSpace::executeUpsert(struct txn *txn, struct space *space,
+			  struct request *request)
+{
+	Index *pk = index_find_unique(space, request->index_id);
+	/* Try to find the tuple by primary key. */
+	const char *key = request->key;
+	uint32_t part_count = mp_decode_array(&key);
+	primary_key_validate(pk->key_def, key, part_count);
+	struct tuple *old_tuple = pk->findByKey(key, part_count);
+
+	if (old_tuple == NULL) {
+		struct tuple *new_tuple = tuple_new(space->format,
+						    request->tuple,
+						    request->tuple_end);
+		TupleGuard guard(new_tuple);
+		space_validate_tuple(space, new_tuple);
+		this->replace(txn, space, NULL, new_tuple, DUP_INSERT);
+	} else {
+		TupleGuard old_guard(old_tuple);
+
+		/* Update the tuple. */
+		struct tuple *new_tuple =
+			tuple_upsert(space->format, region_alloc_cb,
+				     &fiber()->gc, old_tuple,
+				     request->ops, request->ops_end,
+				     request->index_base);
+		TupleGuard guard(new_tuple);
+
+		space_validate_tuple(space, new_tuple);
+		this->replace(txn, space, old_tuple, new_tuple, DUP_REPLACE);
+	}
+
+	txn_commit_stmt(txn);
+	/* Return nothing: upsert does not return data. */
+}
+
+void
+MemtxSpace::onAlter(Handler *old)
+{
+	MemtxSpace *handler = (MemtxSpace *) old;
+	replace = handler->replace;
+}
+
+void
+MemtxSpace::executeSelect(struct txn *, struct space *space,
+			  uint32_t index_id, uint32_t iterator,
+			  uint32_t offset, uint32_t limit,
+			  const char *key, const char * /* key_end */,
+			  struct port *port)
+{
+	MemtxIndex *index = (MemtxIndex *) index_find(space, index_id);
+
+	ERROR_INJECT_EXCEPTION(ERRINJ_TESTING);
+
+	uint32_t found = 0;
+	if (iterator >= iterator_type_MAX)
+		tnt_raise(IllegalParams, "Invalid iterator type");
+	enum iterator_type type = (enum iterator_type) iterator;
+
+	uint32_t part_count = key ? mp_decode_array(&key) : 0;
+	key_validate(index->key_def, type, key, part_count);
+
+	struct iterator *it = index->position();
+	index->initIterator(it, type, key, part_count);
+
+	struct tuple *tuple;
+	while ((tuple = it->next(it)) != NULL) {
+		if (offset > 0) {
+			offset--;
+			continue;
+		}
+		if (limit == found++)
+			break;
+		port_add_tuple(port, tuple);
+	}
+}
 
 static void
 txn_on_yield_or_stop(struct trigger * /* trigger */, void * /* event */)
@@ -138,7 +413,7 @@ memtx_txn_add_undo(struct txn *txn, struct space *space,
 }
 
 /**
- * A short-cut version of space_replace() used during bulk load
+ * A short-cut version of replace() used during bulk load
  * from snapshot.
  */
 void
@@ -158,12 +433,12 @@ memtx_replace_build_next(struct txn * /* txn */, struct space *space,
 		panic("Failed to commit transaction when loading "
 		      "from snapshot");
 	}
-	space->index[0]->buildNext(new_tuple);
+	((MemtxIndex *) space->index[0])->buildNext(new_tuple);
 	tuple_ref(new_tuple);
 }
 
 /**
- * A short-cut version of space_replace() used when loading
+ * A short-cut version of replace() used when loading
  * data from XLOG files.
  */
 void
@@ -223,12 +498,13 @@ memtx_replace_all_keys(struct txn *txn, struct space *space,
 static void
 memtx_end_build_primary_key(struct space *space, void *param)
 {
-	if (space->handler->engine != param || space_index(space, 0) == NULL ||
-	    space->handler->replace == memtx_replace_all_keys)
+	struct MemtxSpace *handler = (struct MemtxSpace *) space->handler;
+	if (handler->engine != param || space_index(space, 0) == NULL ||
+	    handler->replace == memtx_replace_all_keys)
 		return;
 
-	space->index[0]->endBuild();
-	space->handler->replace = memtx_replace_primary_key;
+	((MemtxIndex *) space->index[0])->endBuild();
+	handler->replace = memtx_replace_primary_key;
 }
 
 /**
@@ -240,12 +516,13 @@ memtx_end_build_primary_key(struct space *space, void *param)
 void
 memtx_build_secondary_keys(struct space *space, void *param)
 {
-	if (space->handler->engine != param || space_index(space, 0) == NULL ||
-	    space->handler->replace == memtx_replace_all_keys)
+	struct MemtxSpace *handler = (struct MemtxSpace *) space->handler;
+	if (handler->engine != param || space_index(space, 0) == NULL ||
+	    handler->replace == memtx_replace_all_keys)
 		return;
 
 	if (space->index_id_max > 0) {
-		Index *pk = space->index[0];
+		MemtxIndex *pk = (MemtxIndex *) space->index[0];
 		uint32_t n_tuples = pk->size();
 
 		if (n_tuples > 0) {
@@ -254,13 +531,13 @@ memtx_build_secondary_keys(struct space *space, void *param)
 		}
 
 		for (uint32_t j = 1; j < space->index_count; j++)
-			index_build(space->index[j], pk);
+			index_build((MemtxIndex *) space->index[j], pk);
 
 		if (n_tuples > 0) {
 			say_info("Space '%s': done", space_name(space));
 		}
 	}
-	space->handler->replace = memtx_replace_all_keys;
+	handler->replace = memtx_replace_all_keys;
 }
 
 MemtxEngine::MemtxEngine()
@@ -268,8 +545,7 @@ MemtxEngine::MemtxEngine()
 	m_checkpoint(0),
 	m_state(MEMTX_INITIALIZED)
 {
-	flags = ENGINE_CAN_BE_TEMPORARY |
-		ENGINE_AUTO_CHECK_UPDATE;
+	flags = ENGINE_CAN_BE_TEMPORARY;
 }
 
 /**
@@ -300,9 +576,7 @@ recover_snap(struct recovery_state *r)
 	int64_t signature = vclock_sum(res);
 
 	struct xlog *snap = xlog_open(&r->snap_dir, signature);
-	auto guard = make_scoped_guard([=]{
-		xlog_close(snap);
-	});
+	auto guard = make_scoped_guard([=]{ xlog_close(snap); });
 	/* Save server UUID */
 	r->server_uuid = snap->server_uuid;
 
@@ -343,23 +617,24 @@ Handler *MemtxEngine::open()
 static void
 memtx_add_primary_key(struct space *space, enum memtx_recovery_state state)
 {
+	struct MemtxSpace *handler = (struct MemtxSpace *) space->handler;
 	switch (state) {
 	case MEMTX_INITIALIZED:
 		panic("can't create a new space before snapshot recovery");
 		break;
 	case MEMTX_READING_SNAPSHOT:
-		space->index[0]->beginBuild();
-		space->handler->replace = memtx_replace_build_next;
+		((MemtxIndex *) space->index[0])->beginBuild();
+		handler->replace = memtx_replace_build_next;
 		break;
 	case MEMTX_READING_WAL:
-		space->index[0]->beginBuild();
-		space->index[0]->endBuild();
-		space->handler->replace = memtx_replace_primary_key;
+		((MemtxIndex *) space->index[0])->beginBuild();
+		((MemtxIndex *) space->index[0])->endBuild();
+		handler->replace = memtx_replace_primary_key;
 		break;
 	case MEMTX_OK:
-		space->index[0]->beginBuild();
-		space->index[0]->endBuild();
-		space->handler->replace = memtx_replace_all_keys;
+		((MemtxIndex *) space->index[0])->beginBuild();
+		((MemtxIndex *) space->index[0])->endBuild();
+		handler->replace = memtx_replace_all_keys;
 		break;
 	}
 }
@@ -373,7 +648,8 @@ MemtxEngine::addPrimaryKey(struct space *space)
 void
 MemtxEngine::dropPrimaryKey(struct space *space)
 {
-	space->handler->replace = memtx_replace_no_keys;
+	struct MemtxSpace *handler = (struct MemtxSpace *) space->handler;
+	handler->replace = memtx_replace_no_keys;
 }
 
 void
@@ -385,7 +661,8 @@ MemtxEngine::initSystemSpace(struct space *space)
 bool
 MemtxEngine::needToBuildSecondaryKey(struct space *space)
 {
-	return space->handler->replace == memtx_replace_all_keys;
+	struct MemtxSpace *handler = (struct MemtxSpace *) space->handler;
+	return handler->replace == memtx_replace_all_keys;
 }
 
 Index *
@@ -409,7 +686,7 @@ MemtxEngine::createIndex(struct key_def *key_def)
 void
 MemtxEngine::dropIndex(Index *index)
 {
-	struct iterator *it = index->position();
+	struct iterator *it = ((MemtxIndex*) index)->position();
 	index->initIterator(it, ITER_ALL, NULL, 0);
 	struct tuple *tuple;
 	while ((tuple = it->next(it)))
@@ -505,7 +782,6 @@ MemtxEngine::prepare(struct txn *txn)
 	trigger_clear(&txn->fiber_on_stop);
 }
 
-
 void
 MemtxEngine::beginStatement(struct txn *txn)
 {
@@ -541,10 +817,11 @@ MemtxEngine::rollbackStatement(struct txn_stmt *stmt)
 	assert(stmt->old_tuple || stmt->new_tuple);
 	struct space *space = stmt->space;
 	int index_count;
+	struct MemtxSpace *handler = (struct MemtxSpace *) space->handler;
 
-	if (space->handler->replace == memtx_replace_all_keys)
+	if (handler->replace == memtx_replace_all_keys)
 		index_count = space->index_count;
-	else if (space->handler->replace == memtx_replace_primary_key)
+	else if (handler->replace == memtx_replace_primary_key)
 		index_count = 1;
 	else
 		panic("transaction rolled back during snapshot recovery");
@@ -928,7 +1205,7 @@ memtx_index_extent_alloc()
 		     /* same error as in mempool_alloc */
 		     tnt_raise(OutOfMemory, MEMTX_EXTENT_SIZE,
 			       "mempool", "new slab")
-	);
+		    );
 	return mempool_alloc(&memtx_index_extent_pool);
 }
 
@@ -949,10 +1226,10 @@ void
 memtx_index_extent_reserve(int num)
 {
 	ERROR_INJECT(ERRINJ_INDEX_ALLOC,
-	/* same error as in mempool_alloc */
+		     /* same error as in mempool_alloc */
 		     tnt_raise(OutOfMemory, MEMTX_EXTENT_SIZE,
 			       "mempool", "new slab")
-	);
+		    );
 	while (memtx_index_num_reserved_extents < num) {
 		void *ext = mempool_alloc(&memtx_index_extent_pool);
 		*(void **)ext = memtx_index_reserved_extents;
diff --git a/src/box/memtx_hash.cc b/src/box/memtx_hash.cc
index 8d8bf5c6fa06a93df111c1615f9d7038d26347bc..a0cb9f06b85efc1f10ca9bb91e0b469ce9f0ba6c 100644
--- a/src/box/memtx_hash.cc
+++ b/src/box/memtx_hash.cc
@@ -211,7 +211,7 @@ hash_iterator_eq(struct iterator *it)
 /* {{{ MemtxHash -- implementation of all hashes. **********************/
 
 MemtxHash::MemtxHash(struct key_def *key_def)
-	: Index(key_def)
+	: MemtxIndex(key_def)
 {
 	memtx_index_arena_init();
 	hash_table = (struct light_index_core *) malloc(sizeof(*hash_table));
diff --git a/src/box/memtx_hash.h b/src/box/memtx_hash.h
index 593ca34348cf66829ed34cf0a0300aca7ddf9878..c32746884c0bdbb638caec7507bc8812ddf9c621 100644
--- a/src/box/memtx_hash.h
+++ b/src/box/memtx_hash.h
@@ -31,11 +31,11 @@
  * SUCH DAMAGE.
  */
 
-#include "index.h"
+#include "memtx_index.h"
 
 struct light_index_core;
 
-class MemtxHash: public Index {
+class MemtxHash: public MemtxIndex {
 public:
 	MemtxHash(struct key_def *key_def);
 	~MemtxHash();
diff --git a/src/box/memtx_index.cc b/src/box/memtx_index.cc
new file mode 100644
index 0000000000000000000000000000000000000000..8c0da8c48deddd1d0a301151af4265bb30406c98
--- /dev/null
+++ b/src/box/memtx_index.cc
@@ -0,0 +1,111 @@
+
+/*
+ * Copyright 2010-2015, 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 "index.h"
+#include "memtx_index.h"
+#include "tuple.h"
+#include "say.h"
+#include "schema.h"
+#include "user_def.h"
+#include "space.h"
+
+void
+MemtxIndex::beginBuild()
+{}
+
+void
+MemtxIndex::reserve(uint32_t /* size_hint */)
+{}
+
+void
+MemtxIndex::buildNext(struct tuple *tuple)
+{
+	replace(NULL, tuple, DUP_INSERT);
+}
+
+void
+MemtxIndex::endBuild()
+{}
+
+struct tuple *
+MemtxIndex::min(const char *key, uint32_t part_count) const
+{
+	struct iterator *it = position();
+	initIterator(it, ITER_GE, key, part_count);
+	return it->next(it);
+}
+
+struct tuple *
+MemtxIndex::max(const char *key, uint32_t part_count) const
+{
+	struct iterator *it = position();
+	initIterator(it, ITER_LE, key, part_count);
+	return it->next(it);
+}
+
+size_t
+MemtxIndex::count(enum iterator_type type, const char *key,
+		  uint32_t part_count) const
+{
+	if (type == ITER_ALL && key == NULL)
+		return size(); /* optimization */
+	struct iterator *it = position();
+	initIterator(it, type, key, part_count);
+	size_t count = 0;
+	struct tuple *tuple = NULL;
+	while ((tuple = it->next(it)) != NULL)
+		++count;
+	return count;
+}
+
+void
+index_build(MemtxIndex *index, MemtxIndex *pk)
+{
+	uint32_t n_tuples = pk->size();
+	uint32_t estimated_tuples = n_tuples * 1.2;
+
+	index->beginBuild();
+	index->reserve(estimated_tuples);
+
+	if (n_tuples > 0) {
+		say_info("Adding %" PRIu32 " keys to %s index '%s' ...",
+			 n_tuples, index_type_strs[index->key_def->type],
+			 index_name(index));
+	}
+
+	struct iterator *it = pk->position();
+	pk->initIterator(it, ITER_ALL, NULL, 0);
+	struct tuple *tuple;
+	while ((tuple = it->next(it)))
+		index->buildNext(tuple);
+
+	index->endBuild();
+}
diff --git a/src/box/memtx_index.h b/src/box/memtx_index.h
new file mode 100644
index 0000000000000000000000000000000000000000..8731b74b2a4bca9a5017d11d3c9ed27c2d547174
--- /dev/null
+++ b/src/box/memtx_index.h
@@ -0,0 +1,80 @@
+#ifndef TARANTOOL_BOX_MEMTX_INDEX_H_INCLUDED
+#define TARANTOOL_BOX_MEMTX_INDEX_H_INCLUDED
+/*
+ * Copyright 2010-2015, 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 "index.h"
+
+class MemtxIndex: public Index {
+public:
+	MemtxIndex(struct key_def *key_def)
+		:Index(key_def), m_position(NULL)
+	{ }
+	virtual ~MemtxIndex() {
+		if (m_position != NULL)
+			m_position->free(m_position);
+	}
+	virtual struct tuple *min(const char *key, uint32_t part_count) const;
+	virtual struct tuple *max(const char *key, uint32_t part_count) const;
+	virtual size_t count(enum iterator_type type, const char *key,
+			     uint32_t part_count) const;
+
+	inline struct iterator *position() const
+	{
+		if (m_position == NULL)
+			m_position = allocIterator();
+		return m_position;
+	}
+
+	/**
+	 * Two-phase index creation: begin building, add tuples, finish.
+	 */
+	virtual void beginBuild();
+	/**
+	 * Optional hint, given to the index, about
+	 * the total size of the index. If given,
+	 * is given after beginBuild().
+	 */
+	virtual void reserve(uint32_t /* size_hint */);
+	virtual void buildNext(struct tuple *tuple);
+	virtual void endBuild();
+protected:
+	/*
+	 * Pre-allocated iterator to speed up the main case of
+	 * box_process(). Should not be used elsewhere.
+	 */
+	mutable struct iterator *m_position;
+};
+
+/** Build this index based on the contents of another index. */
+void
+index_build(MemtxIndex *index, MemtxIndex *pk);
+
+#endif /* TARANTOOL_BOX_MEMTX_INDEX_H_INCLUDED */
diff --git a/src/box/memtx_rtree.cc b/src/box/memtx_rtree.cc
index 3d6c7e38bd22a8130f4d9bfbecf6989ea47bf2a6..6c773d89e61b90c09aa7c2170c02aa519ef81282 100644
--- a/src/box/memtx_rtree.cc
+++ b/src/box/memtx_rtree.cc
@@ -38,6 +38,10 @@
 
 /* {{{ Utilities. *************************************************/
 
+/**
+ * Extract coordinates of rectangle from message packed string.
+ * There must be <count> or <count * 2> numbers in that string.
+ */
 inline int
 mp_decode_rect(struct rtree_rect *rect, unsigned dimension,
 	       const char *mp, unsigned count)
@@ -64,6 +68,11 @@ mp_decode_rect(struct rtree_rect *rect, unsigned dimension,
 	return 0;
 }
 
+/**
+ * Extract rectangle from message packed string.
+ * There must be an array with appropriate number of coordinates in
+ * that string.
+ */
 inline int
 mp_decode_rect(struct rtree_rect *rect, unsigned dimension,
 	       const char *mp)
@@ -72,6 +81,23 @@ mp_decode_rect(struct rtree_rect *rect, unsigned dimension,
 	return mp_decode_rect(rect, dimension, mp, size);
 }
 
+/**
+ * Extract rectangle from message packed key.
+ * Due to historical issues,
+ * in key a rectangle could be written in two variants:
+ * a)array with appropriate number of coordinates
+ * b)array with on element - array with appropriate number of coordinates
+ */
+inline int
+mp_decode_rect_from_key(struct rtree_rect *rect, unsigned dimension,
+			const char *mp, uint32_t part_count)
+{
+	if (part_count != 1) /* variant a */
+		return mp_decode_rect(rect, dimension, mp, part_count);
+	else /* variant b */
+		return mp_decode_rect(rect, dimension, mp);
+}
+
 inline void
 extract_rectangle(struct rtree_rect *rect, const struct tuple *tuple,
 		  struct key_def *key_def)
@@ -121,7 +147,7 @@ MemtxRTree::~MemtxRTree()
 }
 
 MemtxRTree::MemtxRTree(struct key_def *key_def)
-	: Index(key_def)
+	: MemtxIndex(key_def)
 {
 	assert(key_def->part_count == 1);
 	assert(key_def->parts[0].type = ARRAY);
@@ -137,8 +163,13 @@ MemtxRTree::MemtxRTree(struct key_def *key_def)
 	}
 
 	memtx_index_arena_init();
+	assert((int)RTREE_EUCLID == (int)RTREE_INDEX_DISTANCE_TYPE_EUCLID);
+	assert((int)RTREE_MANHATTAN == (int)RTREE_INDEX_DISTANCE_TYPE_MANHATTAN);
+	enum rtree_distance_type distance_type =
+		(enum rtree_distance_type)(int)key_def->opts.distance;
 	rtree_init(&m_tree, m_dimension, MEMTX_EXTENT_SIZE,
-		   memtx_index_extent_alloc, memtx_index_extent_free);
+		   memtx_index_extent_alloc, memtx_index_extent_free,
+		   distance_type);
 }
 
 size_t
@@ -160,7 +191,7 @@ MemtxRTree::findByKey(const char *key, uint32_t part_count) const
 	rtree_iterator_init(&iterator);
 
 	rtree_rect rect;
-	if (mp_decode_rect(&rect, m_dimension, key, part_count))
+	if (mp_decode_rect_from_key(&rect, m_dimension, key, part_count))
 		assert(false);
 
 	struct tuple *result = NULL;
@@ -217,7 +248,8 @@ MemtxRTree::initIterator(struct iterator *iterator, enum iterator_type type,
 				  "It is possible to omit "
 				  "key only for ITER_ALL");
 		}
-	} else if (mp_decode_rect(&rect, m_dimension, key, part_count)) {
+	} else if (mp_decode_rect_from_key(&rect, m_dimension,
+					   key, part_count)) {
 		tnt_raise(ClientError, ER_RTREE_RECT,
 			  "Key", m_dimension, m_dimension * 2);
 	}
diff --git a/src/box/memtx_rtree.h b/src/box/memtx_rtree.h
index 0673c5d1f7b08a34679e3bbe91552d275678ec2a..66ff499d3003d2df1730d80db695efe9f8f393aa 100644
--- a/src/box/memtx_rtree.h
+++ b/src/box/memtx_rtree.h
@@ -30,12 +30,11 @@
  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
-
-#include "index.h"
+#include "memtx_index.h"
 
 #include <salad/rtree.h>
 
-class MemtxRTree: public Index
+class MemtxRTree: public MemtxIndex
 {
 public:
 	MemtxRTree(struct key_def *key_def);
diff --git a/src/box/memtx_tree.cc b/src/box/memtx_tree.cc
index 101dc7cb7167d9200556e22bf63b0999efc8b77f..a8054ff062f88dc8f91bc64b945db5cb7472615d 100644
--- a/src/box/memtx_tree.cc
+++ b/src/box/memtx_tree.cc
@@ -184,7 +184,7 @@ tree_iterator_bwd_skip_one_check_next_equality(struct iterator *iterator)
 /* {{{ MemtxTree  **********************************************************/
 
 MemtxTree::MemtxTree(struct key_def *key_def_arg)
-	: Index(key_def_arg), build_array(0), build_array_size(0),
+	: MemtxIndex(key_def_arg), build_array(0), build_array_size(0),
 	  build_array_alloc_size(0)
 {
 	memtx_index_arena_init();
diff --git a/src/box/memtx_tree.h b/src/box/memtx_tree.h
index c626fa1e6bbbc058d660d450ed7f1c8daa163f11..0b4c89dd84e05714777d1f5e9e64604ffa29a321 100644
--- a/src/box/memtx_tree.h
+++ b/src/box/memtx_tree.h
@@ -31,7 +31,7 @@
  * SUCH DAMAGE.
  */
 
-#include "index.h"
+#include "memtx_index.h"
 #include "memtx_engine.h"
 
 struct tuple;
@@ -54,7 +54,7 @@ tree_index_compare_key(const tuple *a, const key_data *b, struct key_def *key_de
 
 #include "salad/bps_tree.h"
 
-class MemtxTree: public Index {
+class MemtxTree: public MemtxIndex {
 public:
 	MemtxTree(struct key_def *key_def);
 	virtual ~MemtxTree();
diff --git a/src/box/request.cc b/src/box/request.cc
index 12e51f9cc14bc7927abdeda737c7e4d7bd44b782..46da90df7a120473abf3497270b44b352502fbd0 100644
--- a/src/box/request.cc
+++ b/src/box/request.cc
@@ -42,195 +42,9 @@
 #include <scoped_guard.h>
 #include "user_def.h"
 #include "iproto_constants.h"
-#include "stat.h"
+#include "rmean.h"
 
-int stat_base;
-
-enum dup_replace_mode
-dup_replace_mode(uint32_t op)
-{
-	return op == IPROTO_INSERT ? DUP_INSERT : DUP_REPLACE_OR_INSERT;
-}
-
-static void
-execute_replace(struct request *request, struct port *port)
-{
-	struct space *space = space_cache_find(request->space_id);
-	struct txn *txn = txn_begin_stmt(request, space);
-
-	access_check_space(space, PRIV_W);
-	struct tuple *new_tuple = tuple_new(space->format, request->tuple,
-					    request->tuple_end);
-	TupleGuard guard(new_tuple);
-	space_validate_tuple(space, new_tuple);
-	enum dup_replace_mode mode = dup_replace_mode(request->type);
-
-	txn_replace(txn, space, NULL, new_tuple, mode);
-	txn_commit_stmt(txn);
-	/*
-	 * Adding result to port must be after possible WAL write.
-	 * The reason is that any yield between port_add_tuple and port_eof
-	 * calls could lead to sending not finished response to iproto socket.
-	 */
-	port_add_tuple(port, new_tuple);
-}
-
-static void
-execute_update(struct request *request, struct port *port)
-{
-	struct space *space = space_cache_find(request->space_id);
-	struct txn *txn = txn_begin_stmt(request, space);
-
-	access_check_space(space, PRIV_W);
-	/* Try to find the tuple by unique key. */
-	Index *pk = index_find_unique(space, request->index_id);
-	const char *key = request->key;
-	uint32_t part_count = mp_decode_array(&key);
-	primary_key_validate(pk->key_def, key, part_count);
-	struct tuple *old_tuple = pk->findByKey(key, part_count);
-
-	if (old_tuple == NULL) {
-		txn_commit_stmt(txn);
-		return;
-	}
-	TupleGuard old_guard(old_tuple);
-
-	/* Update the tuple; legacy, request ops are in request->tuple */
-	struct tuple *new_tuple = tuple_update(space->format,
-					       region_alloc_cb,
-					       &fiber()->gc,
-					       old_tuple, request->tuple,
-					       request->tuple_end,
-					       request->index_base);
-	TupleGuard guard(new_tuple);
-	space_validate_tuple(space, new_tuple);
-	if (! engine_auto_check_update(space->handler->engine->flags))
-		space_check_update(space, old_tuple, new_tuple);
-	txn_replace(txn, space, old_tuple, new_tuple, DUP_REPLACE);
-	txn_commit_stmt(txn);
-	/*
-	 * Adding result to port must be after possible WAL write.
-	 * The reason is that any yield between port_add_tuple and port_eof
-	 * calls could lead to sending not finished response to iproto socket.
-	 */
-	port_add_tuple(port, new_tuple);
-}
-
-static void
-execute_upsert(struct request *request, struct port * /* port */)
-{
-	struct space *space = space_cache_find(request->space_id);
-	struct txn *txn = txn_begin_stmt(request, space);
-
-	access_check_space(space, PRIV_W);
-	Index *pk = index_find_unique(space, request->index_id);
-	/* Try to find the tuple by primary key. */
-	const char *key = request->key;
-	uint32_t part_count = mp_decode_array(&key);
-	primary_key_validate(pk->key_def, key, part_count);
-	struct tuple *old_tuple = pk->findByKey(key, part_count);
-
-	if (old_tuple == NULL) {
-		struct tuple *new_tuple = tuple_new(space->format,
-						    request->tuple,
-						    request->tuple_end);
-		TupleGuard guard(new_tuple);
-		space_validate_tuple(space, new_tuple);
-
-		txn_replace(txn, space, NULL, new_tuple, DUP_INSERT);
-	} else {
-		TupleGuard old_guard(old_tuple);
-
-		/* Update the tuple. */
-		struct tuple *new_tuple =
-			tuple_upsert(space->format, region_alloc_cb,
-				     &fiber()->gc, old_tuple,
-				     request->ops, request->ops_end,
-				     request->index_base);
-		TupleGuard guard(new_tuple);
-
-		space_validate_tuple(space, new_tuple);
-		if (!engine_auto_check_update(space->handler->engine->flags))
-			space_check_update(space, old_tuple, new_tuple);
-		txn_replace(txn, space, old_tuple, new_tuple, DUP_REPLACE);
-	}
-
-	txn_commit_stmt(txn);
-	/* Return nothing: upsert does not return data. */
-}
-
-static void
-execute_delete(struct request *request, struct port *port)
-{
-	struct space *space = space_cache_find(request->space_id);
-	struct txn *txn = txn_begin_stmt(request, space);
-
-	access_check_space(space, PRIV_W);
-
-	/* Try to find the tuple by unique key. */
-	Index *pk = index_find_unique(space, request->index_id);
-	const char *key = request->key;
-	uint32_t part_count = mp_decode_array(&key);
-	primary_key_validate(pk->key_def, key, part_count);
-	struct tuple *old_tuple = pk->findByKey(key, part_count);
-	if (old_tuple == NULL) {
-		txn_commit_stmt(txn);
-		return;
-	}
-	TupleGuard old_guard(old_tuple);
-	txn_replace(txn, space, old_tuple, NULL, DUP_REPLACE_OR_INSERT);
-	txn_commit_stmt(txn);
-	/*
-	 * Adding result to port must be after possible WAL write.
-	 * The reason is that any yield between port_add_tuple and port_eof
-	 * calls could lead to sending not finished response to iproto socket.
-	 */
-	port_add_tuple(port, old_tuple);
-}
-
-static void
-execute_select(struct request *request, struct port *port)
-{
-	struct space *space = space_cache_find(request->space_id);
-	access_check_space(space, PRIV_R);
-	Index *index = index_find(space, request->index_id);
-
-	ERROR_INJECT_EXCEPTION(ERRINJ_TESTING);
-
-	uint32_t found = 0;
-	uint32_t offset = request->offset;
-	uint32_t limit = request->limit;
-	if (request->iterator >= iterator_type_MAX)
-		tnt_raise(IllegalParams, "Invalid iterator type");
-	enum iterator_type type = (enum iterator_type) request->iterator;
-
-	const char *key = request->key;
-
-	uint32_t part_count = key ? mp_decode_array(&key) : 0;
-
-	struct iterator *it = index->position();
-	key_validate(index->key_def, type, key, part_count);
-	index->initIterator(it, type, key, part_count);
-	IteratorGuard it_guard(it);
-
-	struct tuple *tuple;
-	while ((tuple = it->next(it)) != NULL) {
-		TupleGuard tuple_gc(tuple);
-		if (offset > 0) {
-			offset--;
-			continue;
-		}
-		if (limit == found++)
-			break;
-		port_add_tuple(port, tuple);
-	}
-	if (! in_txn()) {
-		 /* no txn is created, so simply collect garbage here */
-		fiber_gc();
-	}
-}
-
-/** }}} */
+struct rmean *rmean_box;
 
 void
 request_create(struct request *request, uint32_t type)
@@ -239,28 +53,6 @@ request_create(struct request *request, uint32_t type)
 	request->type = type;
 }
 
-typedef void (*request_execute_f)(struct request *, struct port *);
-
-void
-process_rw(struct request *request, struct port *port)
-{
-	assert(iproto_type_is_dml(request->type));
-	static const request_execute_f execute_map[] = {
-		NULL, execute_select, execute_replace, execute_replace,
-		execute_update, execute_delete, 0, 0, 0, execute_upsert
-	};
-	request_execute_f fun = execute_map[request->type];
-	assert(fun != NULL);
-	stat_collect(stat_base, request->type, 1);
-	try {
-		fun(request, port);
-		port_eof(port);
-	} catch (Exception *e) {
-		txn_rollback_stmt();
-		throw;
-	}
-}
-
 void
 request_decode(struct request *request, const char *data, uint32_t len)
 {
diff --git a/src/box/request.h b/src/box/request.h
index 606f084e05474dc3803c736da489638763613759..3028cb56ea9edc52bf1cdddb6459caa50ef645e6 100644
--- a/src/box/request.h
+++ b/src/box/request.h
@@ -35,7 +35,9 @@
 
 struct txn;
 struct port;
-extern int stat_base;
+
+/** box statistics */
+extern struct rmean *rmean_box;
 
 struct request
 {
@@ -79,9 +81,6 @@ struct request_replace_body {
 void
 request_create(struct request *request, uint32_t code);
 
-void
-process_rw(struct request *request, struct port *port);
-
 void
 request_decode(struct request *request, const char *data, uint32_t len);
 
diff --git a/src/box/schema.cc b/src/box/schema.cc
index 33e7a40a3a19a8188c5290be9469ab255a5c5e75..c6133cad13c12c3959acddc1ca636078564aab91 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -32,6 +32,7 @@
 #include "user_def.h"
 #include "engine.h"
 #include "space.h"
+#include "memtx_index.h"
 #include "func.h"
 #include "tuple.h"
 #include "assoc.h"
@@ -110,7 +111,6 @@ space_foreach(void (*func)(struct space *sp, void *udata), void *udata)
 		struct iterator *it = pk->allocIterator();
 		auto scoped_guard = make_scoped_guard([=] { it->free(it); });
 		pk->initIterator(it, ITER_GE, key, 1);
-		IteratorGuard it_guard(it);
 
 		struct tuple *tuple;
 		while ((tuple = it->next(it))) {
@@ -201,7 +201,7 @@ schema_find_id(uint32_t system_space_id, uint32_t index_id,
 	       const char *name, uint32_t len)
 {
 	struct space *space = space_cache_find(system_space_id);
-	Index *index = index_find(space, index_id);
+	MemtxIndex *index = index_find_system(space, index_id);
 	char buf[BOX_NAME_MAX * 2];
 	/**
 	 * This is an internal-only method, we should know the
@@ -214,7 +214,6 @@ schema_find_id(uint32_t system_space_id, uint32_t index_id,
 
 	struct iterator *it = index->position();
 	index->initIterator(it, ITER_EQ, buf, 1);
-	IteratorGuard it_guard(it);
 
 	struct tuple *tuple = it->next(it);
 	if (tuple) {
@@ -251,7 +250,7 @@ schema_init()
 	struct space_def def = {
 		BOX_SCHEMA_ID, ADMIN, 0, "_schema", "memtx", false
 	};
-	struct key_opts opts = { true /* is_unique */, 0 /* dimension */ };
+	struct key_opts opts = key_opts_default;
 	struct key_def *key_def = key_def_new(def.id,
 					      0 /* index id */,
 					      "primary", /* name */
@@ -408,11 +407,10 @@ schema_find_grants(const char *type, uint32_t id)
 {
 	struct space *priv = space_cache_find(BOX_PRIV_ID);
 	/** "object" index */
-	Index *index = index_find(priv, 2);
+	MemtxIndex *index = index_find_system(priv, 2);
 	struct iterator *it = index->position();
 	char key[10 + BOX_NAME_MAX];
 	mp_encode_uint(mp_encode_str(key, type, strlen(type)), id);
 	index->initIterator(it, ITER_EQ, key, 2);
-	IteratorGuard it_guard(it);
 	return it->next(it);
 }
diff --git a/src/box/schema.h b/src/box/schema.h
index 89096a7362aa29485ec113eb6e7986e2de35fa40..5c010c87c85c939584f959a0188e11189ffef393 100644
--- a/src/box/schema.h
+++ b/src/box/schema.h
@@ -41,18 +41,23 @@ enum {
 	BOX_SCHEMA_ID = 272,
 	/** Space id of _space. */
 	BOX_SPACE_ID = 280,
+	/** Space id of _vspace view. */
 	BOX_VSPACE_ID = 281,
 	/** Space id of _index. */
 	BOX_INDEX_ID = 288,
+	/** Space id of _vindex view. */
 	BOX_VINDEX_ID = 289,
 	/** Space id of _func. */
 	BOX_FUNC_ID = 296,
+	/** Space id of _vfunc view. */
 	BOX_VFUNC_ID = 297,
 	/** Space id of _user. */
 	BOX_USER_ID = 304,
+	/** Space id of _vuser view. */
 	BOX_VUSER_ID = 305,
 	/** Space id of _priv. */
 	BOX_PRIV_ID = 312,
+	/** Space id of _vpriv view. */
 	BOX_VPRIV_ID = 313,
 	/** Space id of _cluster. */
 	BOX_CLUSTER_ID = 320,
diff --git a/src/box/sophia_engine.cc b/src/box/sophia_engine.cc
index 546092cea9cce02546e9b221bac68ec7954ceda2..a70423b6aa351fe5e6da2e43d09ae2c14c30861f 100644
--- a/src/box/sophia_engine.cc
+++ b/src/box/sophia_engine.cc
@@ -28,6 +28,7 @@
  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
+#include "sophia_index.h"
 #include "sophia_engine.h"
 #include "cfg.h"
 #include "xrow.h"
@@ -35,13 +36,15 @@
 #include "scoped_guard.h"
 #include "txn.h"
 #include "index.h"
-#include "sophia_index.h"
 #include "recovery.h"
 #include "relay.h"
 #include "space.h"
+#include "schema.h"
+#include "port.h"
 #include "request.h"
 #include "iproto_constants.h"
 #include "salad/rlist.h"
+#include <errinj.h>
 #include <sophia.h>
 #include <stdlib.h>
 #include <stdio.h>
@@ -51,49 +54,139 @@
 #include <dirent.h>
 #include <errno.h>
 
-void sophia_raise(void *env)
+void sophia_error(void *env)
 {
-	void *c = sp_ctl(env);
-	void *o = sp_get(c, "sophia.error");
-	char *error = (char *)sp_get(o, "value", NULL);
-	auto scoped_guard =
-		make_scoped_guard([=] { sp_destroy(o); });
-	tnt_raise(ClientError, ER_SOPHIA, error);
+	char *error = (char *)sp_getstring(env, "sophia.error", NULL);
+	char msg[512];
+	snprintf(msg, sizeof(msg), "%s", error);
+	tnt_raise(ClientError, ER_SOPHIA, msg);
 }
 
-void sophia_info(void (*callback)(const char*, const char*, void*), void *arg)
+void sophia_info(void (*cb)(const char*, const char*, int, void*), void *arg)
 {
-	SophiaEngine *engine = (SophiaEngine*)engine_find("sophia");
-	void *env = engine->env;
-	void *c = sp_ctl(env);
-	void *o, *cur = sp_cursor(c);
-	if (cur == NULL)
-		sophia_raise(env);
-	while ((o = sp_get(cur))) {
-		const char *k = (const char *)sp_get(o, "key", NULL);
-		const char *v = (const char *)sp_get(o, "value", NULL);
-		callback(k, v, arg);
+	SophiaEngine *e = (SophiaEngine *)engine_find("sophia");
+	void *cursor = sp_getobject(e->env, NULL);
+	void *o = NULL;
+	int i = 0;
+	while ((o = sp_get(cursor, o))) {
+		char *key = (char *)sp_getstring(o, "key", 0);
+		char *value = (char *)sp_getstring(o, "value", 0);
+		cb(key, value, i, arg);
+		i++;
 	}
-	sp_destroy(cur);
-}
-
-void
-sophia_replace(struct txn * /* txn */, struct space *space,
-	       struct tuple *old_tuple, struct tuple *new_tuple,
-               enum dup_replace_mode mode)
-{
-	Index *index = index_find(space, 0);
-	(void) index->replace(old_tuple, new_tuple, mode);
+	sp_destroy(cursor);
 }
 
 struct SophiaSpace: public Handler {
 	SophiaSpace(Engine*);
+	virtual struct tuple *
+	executeReplace(struct txn*, struct space *space,
+	               struct request *request);
+	virtual struct tuple *
+	executeDelete(struct txn*, struct space *space,
+	              struct request *request);
+	virtual struct tuple *
+	executeUpdate(struct txn*, struct space *space,
+	              struct request *request);
+	virtual void
+	executeUpsert(struct txn*, struct space *space,
+	              struct request *request);
 };
 
+struct tuple *
+SophiaSpace::executeReplace(struct txn *txn, struct space *space,
+                            struct request *request)
+{
+	space_validate_tuple_raw(space, request->tuple);
+	tuple_field_count_validate(space->format, request->tuple);
+
+	/* Switch from INSERT to REPLACE during recovery.
+	 *
+	 * Database might hold newer key version than currenly
+	 * recovered log record.
+	 */
+	enum dup_replace_mode mode = DUP_REPLACE_OR_INSERT;
+	if (request->type == IPROTO_INSERT) {
+		SophiaEngine *engine = (SophiaEngine *)space->handler->engine;
+		if (engine->recovery_complete)
+			mode = DUP_INSERT;
+	}
+	SophiaIndex *index =
+		(SophiaIndex *)index_find(space, 0);
+	index->replace_or_insert(request->tuple, request->tuple_end, mode);
+	txn_commit_stmt(txn);
+	return NULL;
+}
+
+struct tuple *
+SophiaSpace::executeDelete(struct txn *txn, struct space *space,
+                           struct request *request)
+{
+	SophiaIndex *index = (SophiaIndex *)index_find(space, request->index_id);
+	const char *key = request->key;
+	uint32_t part_count = mp_decode_array(&key);
+	primary_key_validate(index->key_def, key, part_count);
+	index->remove(key);
+	txn_commit_stmt(txn);
+	return NULL;
+}
+
+struct tuple *
+SophiaSpace::executeUpdate(struct txn *txn, struct space *space,
+                           struct request *request)
+{
+	/* Try to find the tuple by unique key */
+	SophiaIndex *index = (SophiaIndex *)index_find(space, request->index_id);
+	const char *key = request->key;
+	uint32_t part_count = mp_decode_array(&key);
+	primary_key_validate(index->key_def, key, part_count);
+	struct tuple *old_tuple = index->findByKey(key, part_count);
+
+	if (old_tuple == NULL) {
+		txn_commit_stmt(txn);
+		return NULL;
+	}
+	TupleGuard old_guard(old_tuple);
+
+	/* Do tuple update */
+	struct tuple *new_tuple =
+		tuple_update(space->format,
+		             region_alloc_cb,
+		             &fiber()->gc,
+		             old_tuple, request->tuple,
+		             request->tuple_end,
+		             request->index_base);
+	TupleGuard guard(new_tuple);
+
+	space_validate_tuple(space, new_tuple);
+	space_check_update(space, old_tuple, new_tuple);
+
+	index->replace_or_insert(new_tuple->data,
+	                         new_tuple->data + new_tuple->bsize,
+	                         DUP_REPLACE);
+	txn_commit_stmt(txn);
+	return NULL;
+}
+
+void
+SophiaSpace::executeUpsert(struct txn *txn, struct space *space,
+                           struct request *request)
+{
+	SophiaIndex *index = (SophiaIndex *)index_find(space, request->index_id);
+	const char *key = request->key;
+	uint32_t part_count = mp_decode_array(&key);
+	primary_key_validate(index->key_def, key, part_count);
+	index->upsert(key,
+	              request->ops,
+	              request->ops_end,
+	              request->tuple,
+	              request->tuple_end);
+	txn_commit_stmt(txn);
+}
+
 SophiaSpace::SophiaSpace(Engine *e)
 	:Handler(e)
 {
-	replace = sophia_replace;
 }
 
 SophiaEngine::SophiaEngine()
@@ -106,25 +199,90 @@ SophiaEngine::SophiaEngine()
 	env = NULL;
 }
 
+static inline int
+sophia_poll(SophiaEngine *e)
+{
+	void *req = sp_poll(e->env);
+	if (req == NULL)
+		return 0;
+	struct fiber *fiber =
+		(struct fiber *)sp_getstring(req, "arg", NULL);
+	assert(fiber != NULL);
+	fiber_set_key(fiber, FIBER_RESULT, req);
+	fiber_call(fiber);
+	return 1;
+}
+
+static inline int
+sophia_queue(SophiaEngine *e)
+{
+	return sp_getint(e->env, "scheduler.reqs");
+}
+
+static inline void
+sophia_on_event(void *arg)
+{
+	SophiaEngine *engine = (SophiaEngine *)arg;
+	ev_async_send(engine->cord->loop, &engine->watcher);
+}
+
+static void
+sophia_idle_cb(ev_loop *loop, struct ev_idle *w, int /* events */)
+{
+	SophiaEngine *engine = (SophiaEngine *)w->data;
+	sophia_poll(engine);
+	if (sophia_queue(engine) == 0)
+		ev_idle_stop(loop, w);
+}
+
+static void
+sophia_async_schedule(ev_loop *loop, struct ev_async *w, int /* events */)
+{
+	SophiaEngine *engine = (SophiaEngine *)w->data;
+	sophia_poll(engine);
+	if (sophia_queue(engine))
+		ev_idle_start(loop, &engine->idle);
+}
+
 void
 SophiaEngine::init()
 {
+	cord = cord();
+	ev_idle_init(&idle, sophia_idle_cb);
+	ev_async_init(&watcher, sophia_async_schedule);
+	ev_async_start(cord->loop, &watcher);
+	watcher.data = this;
+	idle.data = this;
 	env = sp_env();
 	if (env == NULL)
 		panic("failed to create sophia environment");
-	void *c = sp_ctl(env);
-	sp_set(c, "sophia.path", cfg_gets("sophia_dir"));
-	sp_set(c, "sophia.path_create", "0");
-	sp_set(c, "scheduler.threads", cfg_gets("sophia.threads"));
-	sp_set(c, "memory.limit", cfg_gets("sophia.memory_limit"));
-	sp_set(c, "compaction.node_size", cfg_gets("sophia.node_size"));
-	sp_set(c, "compaction.page_size", cfg_gets("sophia.page_size"));
-	sp_set(c, "log.enable", "0");
-	sp_set(c, "log.two_phase_recover", "1");
-	sp_set(c, "log.commit_lsn", "1");
+	sp_setint(env, "sophia.path_create", 0);
+	sp_setstring(env, "sophia.path", cfg_gets("sophia_dir"), 0);
+	sp_setstring(env, "scheduler.on_event", (const void *)sophia_on_event, 0);
+	sp_setstring(env, "scheduler.on_event_arg", (const void *)this, 0);
+	sp_setint(env, "scheduler.threads", cfg_geti("sophia.threads"));
+	sp_setint(env, "memory.limit", cfg_geti("sophia.memory_limit"));
+	sp_setint(env, "compaction.node_size", cfg_geti("sophia.node_size"));
+	sp_setint(env, "compaction.page_size", cfg_geti("sophia.page_size"));
+	sp_setint(env, "compaction.0.async", 1);
+	sp_setint(env, "log.enable", 0);
+	sp_setint(env, "log.two_phase_recover", 1);
+	sp_setint(env, "log.commit_lsn", 1);
 	int rc = sp_open(env);
 	if (rc == -1)
-		sophia_raise(env);
+		sophia_error(env);
+}
+
+void
+SophiaEngine::endRecovery()
+{
+	if (recovery_complete)
+		return;
+	/* complete two-phase recovery */
+	int rc = sp_open(env);
+	if (rc == -1)
+		sophia_error(env);
+	recovery_complete = 1;
 }
 
 Handler *
@@ -156,6 +314,35 @@ sophia_send_row(Relay *relay, uint32_t space_id, char *tuple,
 	relay_send(relay, &row);
 }
 
+static inline struct key_def *
+sophia_join_key_def(void *env, void *db)
+{
+	uint32_t id = sp_getint(db, "id");
+	uint32_t count = sp_getint(db, "key-count");
+	struct key_def *key_def;
+	struct key_opts key_opts = key_opts_default;
+	key_def = key_def_new(id, 0, "sophia_join", TREE, &key_opts, count);
+	int i = 0;
+	while (i < count) {
+		char path[64];
+		int len = snprintf(path, sizeof(path), "db.%d.index.key", id);
+		if (i > 0) {
+			snprintf(path + len, sizeof(path) - len, "_%d", i);
+		}
+		char *type = (char *)sp_getstring(env, path, NULL);
+		assert(type != NULL);
+		if (strcmp(type, "string") == 0)
+			key_def->parts[i].type = STRING;
+		else
+		if (strcmp(type, "u64") == 0)
+			key_def->parts[i].type = NUM;
+		free(type);
+		key_def->parts[i].fieldno = i;
+		i++;
+	}
+	return key_def;
+}
+
 /**
  * Relay all data that should be present in the snapshot
  * to the replica.
@@ -171,62 +358,56 @@ SophiaEngine::join(Relay *relay)
 	/* get snapshot object */
 	char id[128];
 	snprintf(id, sizeof(id), "snapshot.%" PRIu64, signt);
-	void *c = sp_ctl(env);
-	void *snapshot = sp_get(c, id);
+	void *snapshot = sp_getobject(env, id);
 	assert(snapshot != NULL);
 
 	/* iterate through a list of databases which took a
 	 * part in the snapshot */
-	void *db_cursor = sp_ctl(snapshot, "db_list");
+	void *db;
+	void *db_cursor = sp_getobject(snapshot, "db-cursor");
 	if (db_cursor == NULL)
-		sophia_raise(env);
-	while (sp_get(db_cursor)) {
-		void *db = sp_object(db_cursor);
-
-		/* get space id */
-		void *dbctl = sp_ctl(db);
-		void *oid = sp_get(dbctl, "name");
-		char *name = (char*)sp_get(oid, "value", NULL);
-		char *pe = NULL;
-		uint32_t space_id = strtoul(name, &pe, 10);
-		sp_destroy(oid);
-
+		sophia_error(env);
+
+	while ((db = sp_get(db_cursor, NULL)))
+	{
+		/* prepare space schema */
+		struct key_def *key_def;
+		try {
+			key_def = sophia_join_key_def(env, db);
+		} catch (...) {
+			sp_destroy(db_cursor);
+			throw;
+		}
 		/* send database */
-		void *o = sp_object(db);
-		void *cursor = sp_cursor(snapshot, o);
+		void *cursor = sp_cursor(snapshot);
 		if (cursor == NULL) {
 			sp_destroy(db_cursor);
-			sophia_raise(env);
+			key_def_delete(key_def);
+			sophia_error(env);
 		}
-		while (sp_get(cursor)) {
-			o = sp_object(cursor);
-			uint32_t tuple_size = 0;
-			char *tuple = (char *)sp_get(o, "value", &tuple_size);
+		void *obj = sp_object(db);
+		while ((obj = sp_get(cursor, obj)))
+		{
+			uint32_t tuple_size;
+			char *tuple = (char *)sophia_tuple_new(obj, key_def, NULL, &tuple_size);
 			try {
-				sophia_send_row(relay, space_id, tuple, tuple_size);
+				sophia_send_row(relay, key_def->space_id, tuple, tuple_size);
 			} catch (...) {
+				key_def_delete(key_def);
+				free(tuple);
+				sp_destroy(obj);
 				sp_destroy(cursor);
 				sp_destroy(db_cursor);
 				throw;
 			}
+			free(tuple);
 		}
 		sp_destroy(cursor);
+		key_def_delete(key_def);
 	}
 	sp_destroy(db_cursor);
 }
 
-void
-SophiaEngine::endRecovery()
-{
-	if (recovery_complete)
-		return;
-	/* complete two-phase recovery */
-	int rc = sp_open(env);
-	if (rc == -1)
-		sophia_raise(env);
-	recovery_complete = 1;
-}
-
 Index*
 SophiaEngine::createIndex(struct key_def *key_def)
 {
@@ -241,21 +422,21 @@ SophiaEngine::createIndex(struct key_def *key_def)
 void
 SophiaEngine::dropIndex(Index *index)
 {
-	SophiaIndex *i = (SophiaIndex*)index;
+	SophiaIndex *i = (SophiaIndex *)index;
 	/* schedule asynchronous drop */
 	int rc = sp_drop(i->db);
 	if (rc == -1)
-		sophia_raise(env);
+		sophia_error(env);
 	/* unref db object */
 	rc = sp_destroy(i->db);
 	if (rc == -1)
-		sophia_raise(env);
+		sophia_error(env);
 	/* maybe start asynchronous database
 	 * shutdown: last snapshot might hold a
 	 * db pointer. */
 	rc = sp_destroy(i->db);
 	if (rc == -1)
-		sophia_raise(env);
+		sophia_error(env);
 	i->db  = NULL;
 	i->env = NULL;
 }
@@ -264,7 +445,7 @@ void
 SophiaEngine::keydefCheck(struct space *space, struct key_def *key_def)
 {
 	switch (key_def->type) {
-	case TREE:
+	case TREE: {
 		if (! key_def->opts.is_unique) {
 			tnt_raise(ClientError, ER_MODIFY_INDEX,
 				  key_def->name,
@@ -277,20 +458,25 @@ SophiaEngine::keydefCheck(struct space *space, struct key_def *key_def)
 				  space_name(space),
 				  "Sophia TREE secondary indexes are not supported");
 		}
-		if (key_def->part_count != 1) {
-			tnt_raise(ClientError, ER_MODIFY_INDEX,
-				  key_def->name,
-				  space_name(space),
-				  "Sophia TREE index key can not be multipart");
-		}
-		if (key_def->parts[0].type != NUM &&
-		    key_def->parts[0].type != STRING) {
-			tnt_raise(ClientError, ER_MODIFY_INDEX,
-				  key_def->name,
-				  space_name(space),
-				  "Sophia TREE index field type must be STR or NUM");
+		int i = 0;
+		while (i < key_def->part_count) {
+			struct key_part *part = &key_def->parts[i];
+			if (part->type != NUM && part->type != STRING) {
+				tnt_raise(ClientError, ER_MODIFY_INDEX,
+				          key_def->name,
+				          space_name(space),
+				          "Sophia TREE index field type must be STR or NUM");
+			}
+			if (part->fieldno != i) {
+				tnt_raise(ClientError, ER_MODIFY_INDEX,
+				          key_def->name,
+				          space_name(space),
+				          "Sophia TREE key-parts must follow first and cannot be sparse");
+			}
+			i++;
 		}
 		break;
+	}
 	default:
 		tnt_raise(ClientError, ER_INDEX_TYPE,
 			  key_def->name,
@@ -306,13 +492,26 @@ SophiaEngine::beginStatement(struct txn *txn)
 	if (txn->n_stmts == 1) {
 		txn->engine_tx = sp_begin(env);
 		if (txn->engine_tx == NULL)
-			sophia_raise(env);
+			sophia_error(env);
 	}
 }
 
 void
 SophiaEngine::prepare(struct txn *txn)
 {
+	/* A half committed transaction is no longer
+	 * being part of concurrent index, but still can be
+	 * commited or rolled back.
+	 *
+	 * This mode disables conflict resolution for 'prepared'
+	 * transactions and solves the issue with concurrent
+	 * write-write conflicts during wal write/yield.
+	 *
+	 * It is important to maintain correct serial
+	 * commit order by wal_writer.
+	 */
+	sp_setint(txn->engine_tx, "half_commit", 1);
+
 	int rc = sp_prepare(txn->engine_tx);
 	switch (rc) {
 	case 1: /* rollback */
@@ -321,7 +520,7 @@ SophiaEngine::prepare(struct txn *txn)
 		tnt_raise(ClientError, ER_TRANSACTION_CONFLICT);
 		break;
 	case -1:
-		sophia_raise(env);
+		sophia_error(env);
 		break;
 	}
 }
@@ -334,7 +533,8 @@ SophiaEngine::commit(struct txn *txn)
 	/* commit transaction using transaction
 	 * commit signature */
 	assert(txn->signature >= 0);
-	int rc = sp_commit(txn->engine_tx, txn->signature);
+	sp_setint(txn->engine_tx, "lsn", txn->signature);
+	int rc = sp_commit(txn->engine_tx);
 	if (rc == -1) {
 		panic("sophia commit failed: txn->signature = %"
 		      PRIu64, txn->signature);
@@ -343,9 +543,9 @@ SophiaEngine::commit(struct txn *txn)
 }
 
 void
-SophiaEngine::rollbackStatement(struct txn_stmt *)
+SophiaEngine::rollbackStatement(struct txn_stmt* /* stmt */)
 {
-	panic("not implemented");
+	say_info("SophiaEngine::rollbackStatement()");
 }
 
 void
@@ -369,21 +569,20 @@ static inline void
 sophia_snapshot(void *env, int64_t lsn)
 {
 	/* start asynchronous checkpoint */
-	void *c = sp_ctl(env);
-	int rc = sp_set(c, "scheduler.checkpoint");
+	int rc = sp_setint(env, "scheduler.checkpoint", 0);
 	if (rc == -1)
-		sophia_raise(env);
+		sophia_error(env);
 	char snapshot[128];
 	snprintf(snapshot, sizeof(snapshot), "snapshot.%" PRIu64, lsn);
 	/* ensure snapshot is not already exists */
-	void *o = sp_get(c, snapshot);
+	void *o = sp_getobject(env, snapshot);
 	if (o) {
 		return;
 	}
 	snprintf(snapshot, sizeof(snapshot), "%" PRIu64, lsn);
-	rc = sp_set(c, "snapshot", snapshot);
+	rc = sp_setstring(env, "snapshot", snapshot, 0);
 	if (rc == -1)
-		sophia_raise(env);
+		sophia_error(env);
 }
 
 static inline void
@@ -393,15 +592,14 @@ sophia_reference_checkpoint(void *env, int64_t lsn)
 	 * engine lsn */
 	char checkpoint_id[32];
 	snprintf(checkpoint_id, sizeof(checkpoint_id), "%" PRIu64, lsn);
-	void *c = sp_ctl(env);
-	int rc = sp_set(c, "snapshot", checkpoint_id);
+	int rc = sp_setstring(env, "snapshot", checkpoint_id, 0);
 	if (rc == -1)
-		sophia_raise(env);
+		sophia_error(env);
 	char snapshot[128];
 	snprintf(snapshot, sizeof(snapshot), "snapshot.%" PRIu64 ".lsn", lsn);
-	rc = sp_set(c, snapshot, checkpoint_id);
+	rc = sp_setint(env, snapshot, lsn);
 	if (rc == -1)
-		sophia_raise(env);
+		sophia_error(env);
 }
 
 static inline int
@@ -410,25 +608,14 @@ sophia_snapshot_ready(void *env, int64_t lsn)
 	/* get sophia lsn associated with snapshot */
 	char snapshot[128];
 	snprintf(snapshot, sizeof(snapshot), "snapshot.%" PRIu64 ".lsn", lsn);
-	void *c = sp_ctl(env);
-	void *o = sp_get(c, snapshot);
-	if (o == NULL) {
+	int64_t snapshot_start_lsn = sp_getint(env, snapshot);
+	if (snapshot_start_lsn == -1) {
 		if (sp_error(env))
-			sophia_raise(env);
+			sophia_error(env);
 		panic("sophia snapshot %" PRIu64 " does not exist", lsn);
 	}
-	char *pe;
-	char *p = (char *)sp_get(o, "value", NULL);
-	int64_t snapshot_start_lsn = strtoull(p, &pe, 10);
-	sp_destroy(o);
-
 	/* compare with a latest completed checkpoint lsn */
-	o = sp_get(c, "scheduler.checkpoint_lsn_last");
-	if (o == NULL)
-		sophia_raise(env);
-	p = (char *)sp_get(o, "value", NULL);
-	int64_t last_lsn = strtoull(p, &pe, 10);
-	sp_destroy(o);
+	int64_t last_lsn = sp_getint(env, "scheduler.checkpoint_lsn_last");
 	return last_lsn >= snapshot_start_lsn;
 }
 
@@ -437,16 +624,15 @@ sophia_delete_checkpoint(void *env, int64_t lsn)
 {
 	char snapshot[128];
 	snprintf(snapshot, sizeof(snapshot), "snapshot.%" PRIu64, lsn);
-	void *c = sp_ctl(env);
-	void *s = sp_get(c, snapshot);
+	void *s = sp_getobject(env, snapshot);
 	if (s == NULL) {
 		if (sp_error(env))
-			sophia_raise(env);
+			sophia_error(env);
 		panic("sophia snapshot %" PRIu64 " does not exist", lsn);
 	}
 	int rc = sp_destroy(s);
 	if (rc == -1)
-		sophia_raise(env);
+		sophia_error(env);
 }
 
 void
@@ -509,11 +695,3 @@ SophiaEngine::abortCheckpoint()
 		m_checkpoint_lsn = -1;
 	}
 }
-
-int sophia_schedule(void)
-{
-	SophiaEngine *engine = (SophiaEngine *)engine_find("sophia");
-	assert(engine->env != NULL);
-	void *c = sp_ctl(engine->env);
-	return sp_set(c, "scheduler.run");
-}
diff --git a/src/box/sophia_engine.h b/src/box/sophia_engine.h
index 735d77448fc94efd20d7a852a80eb7aa01122a7b..c9ffcaee8804e60fa855343b5f8e486e0218b159 100644
--- a/src/box/sophia_engine.h
+++ b/src/box/sophia_engine.h
@@ -31,6 +31,7 @@
  * SUCH DAMAGE.
  */
 #include "engine.h"
+#include "third_party/tarantool_ev.h"
 
 struct SophiaEngine: public Engine {
 	SophiaEngine();
@@ -58,13 +59,12 @@ struct SophiaEngine: public Engine {
 	int64_t m_checkpoint_lsn;
 public:
 	int recovery_complete;
+	struct cord *cord;
+	ev_async watcher;
+	ev_idle idle;
 };
 
-void sophia_info(void (*)(const char*, const char*, void*), void*);
-void sophia_raise(void*);
-
-extern "C" {
-int sophia_schedule(void);
-}
+void sophia_error(void*);
+void sophia_info(void (*)(const char*, const char*, int, void*), void*);
 
 #endif /* TARANTOOL_BOX_SOPHIA_ENGINE_H_INCLUDED */
diff --git a/src/box/sophia_index.cc b/src/box/sophia_index.cc
index 1d5133d5fe417cc068c8eebc31f40947390ae797..eb46f2e70ac3c93a1ac164b3c0c27f08d2102c5c 100644
--- a/src/box/sophia_index.cc
+++ b/src/box/sophia_index.cc
@@ -29,282 +29,584 @@
  * SUCH DAMAGE.
  */
 #include "sophia_index.h"
+#include "sophia_engine.h"
 #include "say.h"
 #include "tuple.h"
+#include "tuple_update.h"
 #include "scoped_guard.h"
 #include "errinj.h"
 #include "schema.h"
 #include "space.h"
 #include "txn.h"
 #include "cfg.h"
-#include "sophia_engine.h"
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sophia.h>
 #include <stdio.h>
 #include <inttypes.h>
 
-enum sophia_op {
-	SOPHIA_SET,
-	SOPHIA_DELETE
-};
-
-static inline void
-sophia_write(void *env, void *db, void *tx, enum sophia_op op,
-             struct key_def *key_def,
-             struct tuple *tuple)
+void*
+sophia_tuple_new(void *obj, struct key_def *key_def,
+                 struct tuple_format *format,
+                 uint32_t *bsize)
 {
-	const char *key = tuple_field(tuple, key_def->parts[0].fieldno);
-	const char *keyptr = key;
-	mp_next(&keyptr);
-	size_t keysize = keyptr - key;
-	void *o = sp_object(db);
-	if (o == NULL)
-		sophia_raise(env);
-	sp_set(o, "key", key, keysize);
-	sp_set(o, "value", tuple->data, tuple->bsize);
-	int rc;
-	switch (op) {
-	case SOPHIA_DELETE:
-		rc = sp_delete(tx, o);
-		break;
-	case SOPHIA_SET:
-		rc = sp_set(tx, o);
-		break;
+	int valuesize = 0;
+	char *value = (char *)sp_getstring(obj, "value", &valuesize);
+	char *valueend = value + valuesize;
+
+	assert(key_def->part_count < 16);
+	struct {
+		const char *part;
+		int size;
+	} parts[16];
+
+	/* prepare keys */
+	int size = 0;
+	int i = 0;
+	while (i < key_def->part_count) {
+		char partname[32];
+		int len = snprintf(partname, sizeof(partname), "key");
+		if (i > 0)
+			 snprintf(partname + len, sizeof(partname) - len, "_%d", i);
+		parts[i].part = (const char *)sp_getstring(obj, partname, &parts[i].size);
+		assert(parts[i].part != NULL);
+		if (key_def->parts[i].type == STRING) {
+			size += mp_sizeof_str(parts[i].size);
+		} else {
+			size += mp_sizeof_uint(*(uint64_t *)parts[i].part);
+		}
+		i++;
+	}
+	int count = key_def->part_count;
+	char *p = value;
+	while (p < valueend) {
+		count++;
+		mp_next((const char **)&p);
+	}
+	size += mp_sizeof_array(count);
+	size += valuesize;
+	if (bsize) {
+		*bsize = size;
 	}
-	if (rc == -1)
-		sophia_raise(env);
-}
 
-static struct tuple*
-sophia_read(void *env, void *db, void *tx, const char *key, size_t keysize,
-            struct tuple_format *format)
-{
-	void *o = sp_object(db);
-	if (o == NULL)
-		sophia_raise(env);
-	sp_set(o, "key", key, keysize);
-	void *result = sp_get((tx) ? tx: db, o);
-	if (result == NULL)
-		return NULL;
-	auto scoped_guard =
-		make_scoped_guard([=] { sp_destroy(result); });
-	int valuesize = 0;
-	void *value = sp_get(result, "value", &valuesize);
-	return tuple_new(format, (char*)value, (char*)value + valuesize);
+	/* build tuple */
+	struct tuple *tuple;
+	char *raw = NULL;
+	if (format) {
+		tuple = tuple_alloc(format, size);
+		p = tuple->data;
+	} else {
+		raw = (char *)malloc(size);
+		if (raw == NULL)
+			tnt_raise(ClientError, ER_MEMORY_ISSUE, size, "tuple");
+		p = raw;
+	}
+	p = mp_encode_array(p, count);
+	for (i = 0; i < key_def->part_count; i++) {
+		if (key_def->parts[i].type == STRING)
+			p = mp_encode_str(p, parts[i].part, parts[i].size);
+		else
+			p = mp_encode_uint(p, *(uint64_t *)parts[i].part);
+	}
+	memcpy(p, value, valuesize);
+	if (format) {
+		try {
+			tuple_init_field_map(format, tuple, (uint32_t *)tuple);
+		} catch (...) {
+			tuple_delete(tuple);
+			throw;
+		}
+		return tuple;
+	}
+	return raw;
 }
 
-struct sophia_format {
-	uint32_t offset;
-	uint16_t size;
-} __attribute__((packed));
+static uint64_t num_parts[16];
 
-static inline int
-sophia_compare(char *a, size_t asz __attribute__((unused)),
-               char *b, size_t bsz __attribute__((unused)),
-               void *arg)
+void*
+SophiaIndex::createObject(const char *key, bool async, const char **keyend)
 {
-	struct key_def *key_def = (struct key_def*)arg;
-	a = a + ((struct sophia_format*)a)[0].offset;
-	b = b + ((struct sophia_format*)b)[0].offset;
-	int rc = tuple_compare_field(a, b, key_def->parts[0].type);
-	return (rc == 0) ? 0 :
-	       ((rc > 0) ? 1 : -1);
+	assert(key_def->part_count < 16);
+	void *host = db;
+	if (async) {
+		host = sp_asynchronous(db);
+	}
+	void *obj = sp_object(host);
+	if (obj == NULL)
+		sophia_error(env);
+	sp_setstring(obj, "arg", fiber(), 0);
+	if (key == NULL)
+		return obj;
+	int i = 0;
+	while (i < key_def->part_count) {
+		char partname[32];
+		int len = snprintf(partname, sizeof(partname), "key");
+		if (i > 0)
+			 snprintf(partname + len, sizeof(partname) - len, "_%d", i);
+		const char *part;
+		uint32_t partsize;
+		if (key_def->parts[i].type == STRING) {
+			part = mp_decode_str(&key, &partsize);
+		} else {
+			num_parts[i] = mp_decode_uint(&key);
+			part = (char *)&num_parts[i];
+			partsize = sizeof(uint64_t);
+		}
+		if (sp_setstring(obj, partname, part, partsize) == -1)
+			sophia_error(env);
+		i++;
+	}
+	if (keyend) {
+		*keyend = key;
+	}
+	return obj;
 }
 
+static int
+sophia_update(int, char*, int, int, char*, int, void*, void**, int*);
+
 static inline void*
 sophia_configure(struct space *space, struct key_def *key_def)
 {
 	SophiaEngine *engine =
-		(SophiaEngine*)space->handler->engine;
+		(SophiaEngine *)space->handler->engine;
 	void *env = engine->env;
-	void *c = sp_ctl(env);
-	char pointer[128];
-	char pointer_arg[128];
-	char name[128];
-	snprintf(name, sizeof(name), "%" PRIu32, key_def->space_id);
-	sp_set(c, "db", name);
-	snprintf(name, sizeof(name), "db.%" PRIu32 ".format",
+	/* create database */
+	char path[128];
+	snprintf(path, sizeof(path), "%" PRIu32, key_def->space_id);
+	sp_setstring(env, "db", path, 0);
+	/* db.id */
+	snprintf(path, sizeof(path), "db.%" PRIu32 ".id",
 	         key_def->space_id);
-	sp_set(c, name, "document");
-	snprintf(name, sizeof(name), "db.%" PRIu32 ".index.cmp",
-	         key_def->space_id);
-	snprintf(pointer, sizeof(pointer), "pointer: %p", (void*)sophia_compare);
-	snprintf(pointer_arg, sizeof(pointer_arg), "pointer: %p", (void*)key_def);
-	sp_set(c, name, pointer, pointer_arg);
-	snprintf(name, sizeof(name), "db.%" PRIu32 ".compression", key_def->space_id);
-	sp_set(c, name, cfg_gets("sophia.compression"));
-	snprintf(name, sizeof(name), "db.%" PRIu32, key_def->space_id);
-	void *db = sp_get(c, name);
+	sp_setint(env, path, key_def->space_id);
+	/* apply space schema */
+	int i = 0;
+	while (i < key_def->part_count)
+	{
+		char *type;
+		if (key_def->parts[i].type == NUM)
+			type = (char *)"u64";
+		else
+			type = (char *)"string";
+		char part[32];
+		if (i == 0) {
+			snprintf(part, sizeof(part), "key");
+		} else {
+			/* create key-part */
+			snprintf(path, sizeof(path), "db.%" PRIu32 ".index",
+			         key_def->space_id);
+			snprintf(part, sizeof(part), "key_%d", i);
+			sp_setstring(env, path, part, 0);
+		}
+		/* set key-part type */
+		snprintf(path, sizeof(path), "db.%" PRIu32 ".index.%s",
+		         key_def->space_id, part);
+		sp_setstring(env, path, type, 0);
+		i++;
+	}
+	/* db.update */
+	snprintf(path, sizeof(path), "db.%" PRIu32 ".index.update", key_def->space_id);
+	sp_setstring(env, path, (const void *)(uintptr_t)sophia_update, 0);
+	/* db.update_arg */
+	snprintf(path, sizeof(path), "db.%" PRIu32 ".index.update_arg", key_def->space_id);
+	sp_setstring(env, path, (const void *)key_def, 0);
+	/* db.compression */
+	snprintf(path, sizeof(path), "db.%" PRIu32 ".compression", key_def->space_id);
+	sp_setint(env, path, cfg_geti("sophia.compression"));
+	/* db.compression_key */
+	snprintf(path, sizeof(path), "db.%" PRIu32 ".compression_key", key_def->space_id);
+	sp_setint(env, path, cfg_geti("sophia.compression_key"));
+	/* db.path_fail_on_drop */
+	snprintf(path, sizeof(path), "db.%" PRIu32 ".path_fail_on_drop", key_def->space_id);
+	sp_setint(env, path, 0);
+	/* db */
+	snprintf(path, sizeof(path), "db.%" PRIu32, key_def->space_id);
+	void *db = sp_getobject(env, path);
 	if (db == NULL)
-		sophia_raise(env);
+		sophia_error(env);
 	return db;
 }
 
-SophiaIndex::SophiaIndex(struct key_def *key_def_arg __attribute__((unused)))
+SophiaIndex::SophiaIndex(struct key_def *key_def_arg)
 	: Index(key_def_arg)
 {
 	struct space *space = space_cache_find(key_def->space_id);
 	SophiaEngine *engine =
-		(SophiaEngine*)space->handler->engine;
+		(SophiaEngine *)space->handler->engine;
 	env = engine->env;
 	db = sophia_configure(space, key_def);
 	if (db == NULL)
-		sophia_raise(env);
+		sophia_error(env);
 	/* start two-phase recovery for a space:
 	 * a. created after snapshot recovery
 	 * b. created during log recovery
 	*/
 	int rc = sp_open(db);
 	if (rc == -1)
-		sophia_raise(env);
-	tuple_format_ref(space->format, 1);
+		sophia_error(env);
+	format = space->format;
+	tuple_format_ref(format, 1);
 }
 
 SophiaIndex::~SophiaIndex()
 {
-	if (m_position != NULL) {
-		m_position->free(m_position);
-		m_position = NULL;
-	}
-	if (db) {
-		int rc = sp_destroy(db);
-		if (rc == 0)
-			return;
-		void *c = sp_ctl(env);
-		void *o = sp_get(c, "sophia.error");
-		char *error = (char *)sp_get(o, "value", NULL);
-		say_info("sophia space %d close error: %s",
-		         key_def->space_id, error);
-		sp_destroy(o);
-	}
+	if (db == NULL)
+		return;
+	int rc = sp_destroy(db);
+	if (rc == 0)
+		return;
+	char *error = (char *)sp_getstring(env, "sophia.error", 0);
+	say_info("sophia space %d close error: %s",
+			 key_def->space_id, error);
+	free(error);
 }
 
 size_t
 SophiaIndex::size() const
 {
-	void *c = sp_ctl(env);
 	char name[128];
 	snprintf(name, sizeof(name), "db.%" PRIu32 ".index.count",
 	         key_def->space_id);
-	void *o = sp_get(c, name);
-	if (o == NULL)
-		sophia_raise(env);
-	uint64_t count = atoi((const char *)sp_get(o, "value", NULL));
-	sp_destroy(o);
-	return count;
+	return sp_getint(env, name);
 }
 
 size_t
 SophiaIndex::bsize() const
 {
-	void *c = sp_ctl(env);
 	char name[128];
 	snprintf(name, sizeof(name), "db.%" PRIu32 ".index.memory_used",
 	         key_def->space_id);
-	void *o = sp_get(c, name);
-	if (o == NULL)
-		sophia_raise(env);
-	uint64_t used = atoi((const char *)sp_get(o, "value", NULL));
-	sp_destroy(o);
-	return used;
+	return sp_getint(env, name);
 }
 
 struct tuple *
-SophiaIndex::findByKey(const char *key, uint32_t part_count) const
+SophiaIndex::findByKey(const char *key, uint32_t part_count = 0) const
 {
-	(void) part_count;
-	assert(part_count == 1);
-	assert(key_def->opts.is_unique && part_count == key_def->part_count);
-	const char *keyptr = key;
-	mp_next(&keyptr);
-	size_t keysize = keyptr - key;
-	struct space *space = space_cache_find(key_def->space_id);
-	void *tx = in_txn() ? in_txn()->engine_tx : NULL;
-	return sophia_read(env, db, tx, key, keysize, space->format);
+	(void)part_count;
+	void *obj = ((SophiaIndex *)this)->createObject(key, true, NULL);
+	void *transaction = db;
+	if (in_txn())
+		transaction = in_txn()->engine_tx;
+	obj = sp_get(transaction, obj);
+	if (obj == NULL)
+		return NULL;
+	int rc = sp_getint(obj, "status");
+	if (rc == 0) {
+		sp_destroy(obj);
+		fiber_yield();
+		obj = fiber_get_key(fiber(), FIBER_RESULT);
+		if (obj == NULL)
+			return NULL;
+		rc = sp_getint(obj, "status");
+		if (rc <= 0 || rc == 2) {
+			sp_destroy(obj);
+			return NULL;
+		}
+	}
+	struct tuple *tuple =
+		(struct tuple *)sophia_tuple_new(obj, key_def, format, NULL);
+	sp_destroy(obj);
+	return tuple;
 }
 
 struct tuple *
-SophiaIndex::replace(struct tuple *old_tuple, struct tuple *new_tuple,
-                     enum dup_replace_mode mode)
+SophiaIndex::replace(struct tuple*, struct tuple*, enum dup_replace_mode)
 {
-	struct space *space = space_cache_find(key_def->space_id);
-	struct txn *txn = in_txn();
-	assert(txn != NULL && txn->engine_tx != NULL);
-	void *tx = txn->engine_tx;
-
-	/* This method does not return old tuple for replace,
-	 * insert or update.
+	/* This method is unused by sophia index.
 	 *
-	 * Delete does return old tuple to be properly
-	 * scheduled for wal write.
-	 */
+	 * see ::replace_or_insert() */
+	assert(0);
+	return NULL;
+}
 
-	/* Switch from INSERT to REPLACE during recovery.
-	 *
-	 * Database might hold newer key version than currenly
-	 * recovered log record.
-	 */
-	if (mode == DUP_INSERT) {
-		SophiaEngine *engine = (SophiaEngine*)space->handler->engine;
-		if (! engine->recovery_complete)
-			mode = DUP_REPLACE_OR_INSERT;
+static void *
+sophia_update_alloc(void *, size_t size)
+{
+	return malloc(size);
+}
+
+struct sophiaref {
+	uint32_t offset;
+	uint16_t size;
+} __attribute__((packed));
+
+static inline char*
+sophia_upsert_to_tarantool(struct key_def *key_def, char *origin, int origin_size,
+                           uint32_t *origin_keysize,
+                           uint32_t *size)
+{
+	struct sophiaref *ref = (struct sophiaref *)origin;
+	uint32_t src_keysize_mp = 0;
+	*origin_keysize = 0;
+
+	/* calculate src msgpack size */
+	int i = 0;
+	while (i < key_def->part_count) {
+		char *ptr;
+		if (key_def->parts[i].type == STRING) {
+			src_keysize_mp += mp_sizeof_str(ref[i].size);
+		} else {
+			ptr = origin + ref[i].offset;
+			src_keysize_mp += mp_sizeof_uint(*(uint64_t *)ptr);
+		}
+		*origin_keysize += sizeof(struct sophiaref) + ref[i].size;
+		i++;
 	}
 
-	/* delete */
-	if (old_tuple && new_tuple == NULL) {
-		sophia_write(env, db, tx, SOPHIA_DELETE, key_def, old_tuple);
-		return old_tuple;
+	/* convert src to msgpack */
+	int valueoffset =
+		ref[key_def->part_count-1].offset +
+		ref[key_def->part_count-1].size;
+	int valuesize = origin_size - valueoffset;
+
+	int count = key_def->part_count;
+	const char *p = origin + valueoffset;
+	while (p < (origin + origin_size)) {
+		count++;
+		mp_next((const char **)&p);
 	}
 
-	/* update */
-	if (old_tuple && new_tuple) {
-		/* assume no primary key update is supported */
-		sophia_write(env, db, tx, SOPHIA_SET, key_def, new_tuple);
+	int src_size = mp_sizeof_array(count) +
+		src_keysize_mp + valuesize;
+	char *src = (char *)malloc(src_size);
+	char *src_ptr = src;
+	if (src == NULL)
 		return NULL;
-	}
 
-	/* insert or replace */
-	switch (mode) {
-	case DUP_INSERT: {
-		const char *key = tuple_field(new_tuple, key_def->parts[0].fieldno);
-		const char *keyptr = key;
-		mp_next(&keyptr);
-		size_t keysize = keyptr - key;
-		struct tuple *dup_tuple =
-			sophia_read(env, db, tx, key, keysize, space->format);
-		if (dup_tuple) {
-			int error = tuple_compare(dup_tuple, new_tuple, key_def) == 0;
-			tuple_delete(dup_tuple);
-			if (error) {
-				struct space *sp =
-					space_cache_find(key_def->space_id);
-				tnt_raise(ClientError, ER_TUPLE_FOUND,
-					  index_name(this), space_name(sp));
-			}
+	src_ptr = mp_encode_array(src_ptr, count);
+	i = 0;
+	while (i < key_def->part_count) {
+		char *ptr = origin + ref[i].offset;
+		if (key_def->parts[i].type == STRING) {
+			src_ptr = mp_encode_str(src_ptr, ptr, ref[i].size);
+		} else {
+			src_ptr = mp_encode_uint(src_ptr, *(uint64_t *)ptr);
 		}
+		i++;
 	}
-	case DUP_REPLACE_OR_INSERT:
-		sophia_write(env, db, tx, SOPHIA_SET, key_def, new_tuple);
-		break;
-	case DUP_REPLACE:
-	default:
-		assert(0);
-		break;
+	memcpy(src_ptr, origin + valueoffset, valuesize);
+	src_ptr += valuesize;
+	assert((src_ptr - src) == src_size);
+
+	*size = src_size;
+	return src;
+}
+
+static inline char*
+sophia_upsert_to_sophia(struct key_def *key_def, char *dest, int dest_size,
+                        char *key, int key_size,
+                        int *size)
+{
+	const char *p = dest;
+	int i = 0;
+	mp_decode_array(&p);
+	while (i < key_def->part_count) {
+		mp_next(&p);
+		i++;
 	}
-	return NULL;
+	const char *dest_value = p;
+	uint32_t dest_value_size = dest_size - (p - dest);
+	*size = key_size + dest_value_size;
+	char *cnv = (char *)malloc(*size);
+	if (cnv == NULL)
+		return NULL;
+	p = cnv;
+	memcpy((void *)p, (void *)key, key_size);
+	p += key_size;
+	memcpy((void *)p, (void *)dest_value, dest_value_size);
+	p += dest_value_size;
+	assert((p - cnv) == *size);
+	return cnv;
+}
+
+static inline char*
+sophia_upsert_default(struct key_def *key_def, char *update, int update_size,
+                      uint32_t *origin_keysize,
+                      uint32_t *size)
+{
+	/* calculate keysize */
+	struct sophiaref *ref = (struct sophiaref *)update;
+	*origin_keysize = 0;
+	int i = 0;
+	while (i < key_def->part_count) {
+		*origin_keysize += sizeof(struct sophiaref) + ref[i].size;
+		i++;
+	}
+	/* upsert using default tuple */
+	char *p = update + *origin_keysize;
+	uint32_t default_tuple_size = *(uint32_t *)p;
+	p += sizeof(uint32_t);
+	char *default_tuple = p;
+	char *default_tuple_end = p + default_tuple_size;
+	p += default_tuple_size;
+	char *expr = p;
+	char *expr_end = update + update_size;
+	const char *up;
+	try {
+		up = tuple_upsert_execute(sophia_update_alloc, NULL,
+		                          expr,
+		                          expr_end,
+		                          default_tuple,
+		                          default_tuple_end,
+		                          size, 1);
+	} catch (...) {
+		return NULL;
+	}
+	return (char *)up;
+}
+
+static inline char*
+sophia_upsert(char *src, int src_size, char *update, int update_size,
+              uint32_t origin_keysize,
+              uint32_t *size)
+{
+	char *p = update + origin_keysize;
+	uint32_t default_tuple_size = *(uint32_t *)p;
+	p += sizeof(uint32_t);
+	char *default_tuple = p;
+	char *default_tuple_end = p + default_tuple_size;
+	(void)default_tuple;
+	(void)default_tuple_end;
+	p += default_tuple_size;
+	char *expr = p;
+	char *expr_end = update + update_size;
+	const char *up;
+	try {
+		up = tuple_upsert_execute(sophia_update_alloc, NULL,
+		                          expr,
+		                          expr_end,
+		                          src,
+		                          src + src_size,
+		                          size, 1);
+	} catch (...) {
+		return NULL;
+	}
+	return (char *)up;
+}
+
+static int
+sophia_update(int origin_flags, char *origin, int origin_size,
+              int update_flags, char *update, int update_size,
+              void *arg,
+              void **result, int *size)
+{
+	(void)origin_flags;
+	(void)update_flags;
+	struct key_def *key_def = (struct key_def *)arg;
+	uint32_t origin_keysize;
+	uint32_t src_size;
+	char *src;
+	uint32_t dest_size;
+	char *dest;
+	if (origin) {
+		/* convert origin object to msgpack */
+		src = sophia_upsert_to_tarantool(key_def, origin, origin_size,
+		                                 &origin_keysize,
+		                                 &src_size);
+		if (src == NULL)
+			return -1;
+		/* execute upsert */
+		dest = sophia_upsert(src, src_size, update, update_size,
+		                     origin_keysize, &dest_size);
+		free(src);
+	} else {
+		/* use default tuple from update */
+		dest = sophia_upsert_default(key_def, update, update_size,
+		                             &origin_keysize, &dest_size);
+		origin = update;
+	}
+	if (dest == NULL)
+		return -1;
+
+	/* convert msgpack to sophia format */
+	*result = sophia_upsert_to_sophia(key_def, dest, dest_size, origin,
+	                                  origin_keysize, size);
+	free(dest);
+	return (*result == NULL) ? -1 : 0;
+}
+
+void
+SophiaIndex::upsert(const char *key,
+                    const char *expr,
+                    const char *expr_end,
+                    const char *tuple,
+                    const char *tuple_end)
+{
+	uint32_t tuple_size = tuple_end - tuple;
+	uint32_t expr_size = expr_end - expr;
+	uint32_t valuesize =
+		sizeof(uint32_t) + tuple_size + expr_size;
+	char *value = (char *)malloc(valuesize);
+	if (value == NULL) {
+	}
+	char *p = value;
+	memcpy(p, &tuple_size, sizeof(uint32_t));
+	p += sizeof(uint32_t);
+	memcpy(p, tuple, tuple_size);
+	p += tuple_size;
+	memcpy(p, expr, expr_size);
+	void *transaction = in_txn()->engine_tx;
+	void *obj = createObject(key, false, NULL);
+	sp_setstring(obj, "value", value, valuesize);
+	int rc = sp_update(transaction, obj);
+	free(value);
+	if (rc == -1)
+		sophia_error(env);
+}
+
+void
+SophiaIndex::replace_or_insert(const char *tuple,
+                               const char *tuple_end,
+                               enum dup_replace_mode mode)
+{
+	uint32_t size = tuple_end - tuple;
+	const char *key = tuple_field_raw(tuple, size, key_def->parts[0].fieldno);
+	/* insert: ensure key does not exists */
+	if (mode == DUP_INSERT) {
+		struct tuple *found = findByKey(key);
+		if (found) {
+			tuple_delete(found);
+			struct space *sp = space_cache_find(key_def->space_id);
+			tnt_raise(ClientError, ER_TUPLE_FOUND,
+			          index_name(this), space_name(sp));
+		}
+	}
+
+	/* replace */
+	void *transaction = in_txn()->engine_tx;
+	const char *value;
+	size_t valuesize;
+	void *obj = createObject(key, false, &value);
+	valuesize = size - (value - tuple);
+	if (valuesize > 0)
+		sp_setstring(obj, "value", value, valuesize);
+	int rc;
+	rc = sp_set(transaction, obj);
+	if (rc == -1)
+		sophia_error(env);
+}
+
+void
+SophiaIndex::remove(const char *key)
+{
+	void *obj = createObject(key, false, NULL);
+	void *transaction = in_txn()->engine_tx;
+	int rc = sp_delete(transaction, obj);
+	if (rc == -1)
+		sophia_error(env);
 }
 
 struct sophia_iterator {
 	struct iterator base;
 	const char *key;
-	int keysize;
-	uint32_t part_count;
+	const char *keyend;
 	struct space *space;
+	struct key_def *key_def;
+	int open;
 	void *env;
 	void *db;
 	void *cursor;
-	void *tx;
+	void *current;
 };
 
 void
@@ -312,20 +614,15 @@ sophia_iterator_free(struct iterator *ptr)
 {
 	assert(ptr->free == sophia_iterator_free);
 	struct sophia_iterator *it = (struct sophia_iterator *) ptr;
-	if (it->cursor)
-		sp_destroy(it->cursor);
-	free(ptr);
-}
-
-void
-sophia_iterator_close(struct iterator *ptr)
-{
-	assert(ptr->free == sophia_iterator_free);
-	struct sophia_iterator *it = (struct sophia_iterator *) ptr;
+	if (it->current) {
+		sp_destroy(it->current);
+		it->current = NULL;
+	}
 	if (it->cursor) {
 		sp_destroy(it->cursor);
 		it->cursor = NULL;
 	}
+	free(ptr);
 }
 
 struct tuple *
@@ -334,12 +631,34 @@ sophia_iterator_next(struct iterator *ptr)
 	assert(ptr->next == sophia_iterator_next);
 	struct sophia_iterator *it = (struct sophia_iterator *) ptr;
 	assert(it->cursor != NULL);
-	void *o = sp_get(it->cursor);
-	if (o == NULL)
+	if (it->open) {
+		it->open = 0;
+		if (it->current) {
+			return (struct tuple *)
+				sophia_tuple_new(it->current, it->key_def,
+				                 it->space->format,
+				                 NULL);
+		} else {
+			return NULL;
+		}
+	}
+	void *obj = sp_get(it->cursor, it->current);
+	it->current = NULL;
+	if (obj == NULL)
 		return NULL;
-	int valuesize = 0;
-	const char *value = (const char*)sp_get(o, "value", &valuesize);
-	return tuple_new(it->space->format, value, value + valuesize);
+	sp_destroy(obj);
+	fiber_yield();
+	obj = fiber_get_key(fiber(), FIBER_RESULT);
+	if (obj == NULL)
+		return NULL;
+	int rc = sp_getint(obj, "status");
+	if (rc <= 0) {
+		sp_destroy(obj);
+		return NULL;
+	}
+	it->current = obj;
+	return (struct tuple *)
+		sophia_tuple_new(obj, it->key_def, it->space->format, NULL);
 }
 
 struct tuple *
@@ -354,8 +673,8 @@ sophia_iterator_eq(struct iterator *ptr)
 	ptr->next = sophia_iterator_last;
 	struct sophia_iterator *it = (struct sophia_iterator *) ptr;
 	assert(it->cursor == NULL);
-	return sophia_read(it->env, it->db, it->tx, it->key, it->keysize,
-	                   it->space->format);
+	SophiaIndex *index = (SophiaIndex *)index_find(it->space, 0);
+	return index->findByKey(it->key);
 }
 
 struct iterator *
@@ -368,9 +687,8 @@ SophiaIndex::allocIterator() const
 		          sizeof(struct sophia_iterator), "SophiaIndex",
 		          "iterator");
 	}
-	it->base.next  = sophia_iterator_next;
-	it->base.close = sophia_iterator_close;
-	it->base.free  = sophia_iterator_free;
+	it->base.next = sophia_iterator_next;
+	it->base.free = sophia_iterator_free;
 	it->cursor = NULL;
 	return (struct iterator *) it;
 }
@@ -382,27 +700,20 @@ SophiaIndex::initIterator(struct iterator *ptr,
 {
 	struct sophia_iterator *it = (struct sophia_iterator *) ptr;
 	assert(it->cursor == NULL);
-	size_t keysize;
-	if (part_count > 0) {
-		const char *keyptr = key;
-		mp_next(&keyptr);
-		keysize = keyptr - key;
-	} else {
-		keysize = 0;
+	if (part_count == 0) {
 		key = NULL;
 	}
 	it->key = key;
-	it->keysize = keysize;
-	it->part_count = part_count;
+	it->key_def = key_def;
 	it->env = env;
 	it->db = db;
 	it->space = space_cache_find(key_def->space_id);
-	it->tx = NULL;
+	it->current = NULL;
+	it->open = 1;
 	const char *compare;
 	switch (type) {
 	case ITER_EQ:
 		it->base.next = sophia_iterator_eq;
-		it->tx = in_txn() ? in_txn()->engine_tx : NULL;
 		return;
 	case ITER_ALL:
 	case ITER_GE: compare = ">=";
@@ -418,13 +729,31 @@ SophiaIndex::initIterator(struct iterator *ptr,
 		          "SophiaIndex", "requested iterator type");
 	}
 	it->base.next = sophia_iterator_next;
-	void *o = sp_object(db);
-	if (o == NULL)
-		sophia_raise(env);
-	sp_set(o, "order", compare);
-	if (key)
-		sp_set(o, "key", key, keysize);
-	it->cursor = sp_cursor(db, o);
+	it->cursor = sp_cursor(env);
 	if (it->cursor == NULL)
-		sophia_raise(env);
+		sophia_error(env);
+	void *obj = ((SophiaIndex *)this)->createObject(key, true, &it->keyend);
+	sp_setstring(obj, "order", compare, 0);
+	/* position first key here, since key pointer might be
+	 * unavailable from lua */
+	obj = sp_get(it->cursor, obj);
+	if (obj == NULL) {
+		sp_destroy(it->cursor);
+		it->cursor = NULL;
+		return;
+	}
+	sp_destroy(obj);
+	fiber_yield();
+	obj = fiber_get_key(fiber(), FIBER_RESULT);
+	if (obj == NULL) {
+		it->current = NULL;
+		return;
+	}
+	int rc = sp_getint(obj, "status");
+	if (rc <= 0) {
+		it->current = NULL;
+		sp_destroy(obj);
+		return;
+	}
+	it->current = obj;
 }
diff --git a/src/box/sophia_index.h b/src/box/sophia_index.h
index a35d6acf60f649d6a4cd05467e7f3604cfcd0ccd..1cfe450b0b2cfc83e6c083fa9ffa461609bc5b51 100644
--- a/src/box/sophia_index.h
+++ b/src/box/sophia_index.h
@@ -38,19 +38,44 @@ class SophiaIndex: public Index {
 	SophiaIndex(struct key_def *key_def);
 	~SophiaIndex();
 
-	virtual size_t size() const;
-	virtual struct tuple *findByKey(const char *key, uint32_t part_count) const;
-	virtual struct tuple *replace(struct tuple *old_tuple,
-				      struct tuple *new_tuple,
-				      enum dup_replace_mode mode);
-	virtual struct iterator *allocIterator() const;
-	virtual void initIterator(struct iterator *iterator,
-				  enum iterator_type type,
-				  const char *key, uint32_t part_count) const;
+	virtual struct tuple*
+	replace(struct tuple*,
+	        struct tuple*, enum dup_replace_mode);
+
+	virtual struct tuple*
+	findByKey(const char *key, uint32_t) const;
+
+	virtual struct iterator*
+	allocIterator() const;
+
+	virtual void
+	initIterator(struct iterator *iterator,
+	             enum iterator_type type,
+	             const char *key, uint32_t part_count) const;
+
+	virtual size_t  size() const;
 	virtual size_t bsize() const;
 
+public:
+	void replace_or_insert(const char *tuple,
+	                       const char *tuple_end,
+	                       enum dup_replace_mode mode);
+	void remove(const char *key);
+	void upsert(const char *key,
+	            const char *ops,
+	            const char *ops_end,
+	            const char *tuple,
+	            const char *tuple_end);
 	void *env;
 	void *db;
+
+private:
+	void *createObject(const char *key, bool async, const char **keyend);
+	struct tuple_format *format;
 };
 
+void *sophia_tuple_new(void*, struct key_def*,
+                       struct tuple_format*,
+                       uint32_t*);
+
 #endif /* TARANTOOL_BOX_SOPHIA_INDEX_H_INCLUDED */
diff --git a/src/box/space.cc b/src/box/space.cc
index d3b89d5fb79210b16dbb77788ce52a162da62502..ffb9dcdb88d44d6ef6002bc5edff86a02bc7cf2e 100644
--- a/src/box/space.cc
+++ b/src/box/space.cc
@@ -141,13 +141,26 @@ space_size(struct space *space)
 	return space_index(space, 0)->size();
 }
 
+static inline void
+space_validate_field_count(struct space *sp, uint32_t field_count)
+{
+	if (sp->def.field_count > 0 && sp->def.field_count != field_count)
+		tnt_raise(ClientError, ER_SPACE_FIELD_COUNT,
+		          field_count, space_name(sp), sp->def.field_count);
+}
+
+void
+space_validate_tuple_raw(struct space *sp, const char *data)
+{
+	uint32_t field_count = mp_decode_array(&data);
+	space_validate_field_count(sp, field_count);
+}
+
 void
 space_validate_tuple(struct space *sp, struct tuple *new_tuple)
 {
 	uint32_t field_count = tuple_field_count(new_tuple);
-	if (sp->def.field_count > 0 && sp->def.field_count != field_count)
-		tnt_raise(ClientError, ER_SPACE_FIELD_COUNT,
-			  field_count, space_name(sp), sp->def.field_count);
+	space_validate_field_count(sp, field_count);
 }
 
 void
@@ -195,6 +208,17 @@ space_stat(struct space *sp)
 	return &space_stat;
 }
 
+/**
+ * We do not allow changes of the primary key during
+ * update.
+ * The syntax of update operation allows the user to primary
+ * key of a tuple, which is prohibited, to avoid funny
+ * effects during replication. Some engines can
+ * track down this situation and abort the operation;
+ * such engines (memtx) don't use this function.
+ * Other engines can't do it, so they ask the server to
+ * verify that the primary key of the tuple has not changed.
+ */
 void
 space_check_update(struct space *space,
 		   struct tuple *old_tuple,
diff --git a/src/box/space.h b/src/box/space.h
index 10490152d8d7cbcc5562e0b8296aee0e60722f9f..60095608b72313ba0ead647caed4eff0e3205e6c 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -45,7 +45,7 @@ struct space {
 	 */
 	Handler *handler;
 
-	/** Triggers fired after space_replace() -- see txn_replace(). */
+	/** Triggers fired after space_replace() -- see txn_commit_stmt(). */
 	struct rlist on_replace;
 	/**
 	 * The number of *enabled* indexes in the space.
@@ -120,6 +120,9 @@ space_size(struct space *space);
 void
 space_validate_tuple(struct space *sp, struct tuple *new_tuple);
 
+void
+space_validate_tuple_raw(struct space *sp, const char *data);
+
 /**
  * Allocate and initialize a space. The space
  * needs to be loaded before it can be used
@@ -187,6 +190,24 @@ index_find_unique(struct space *space, uint32_t index_id)
 	return index;
 }
 
+class MemtxIndex;
+
+/**
+ * Find an index in a system space. Throw an error
+ * if we somehow deal with a non-memtx space (it can't
+ * be used for system spaces.
+ */
+static inline MemtxIndex *
+index_find_system(struct space *space, uint32_t index_id)
+{
+	if (! space_is_memtx(space)) {
+		tnt_raise(ClientError, ER_UNSUPPORTED,
+			  space->handler->engine->name, "system data");
+	}
+	return (MemtxIndex *) index_find(space, index_id);
+}
+
+
 extern "C" void
 space_run_triggers(struct space *space, bool yesno);
 
diff --git a/src/box/sysview_engine.cc b/src/box/sysview_engine.cc
index 5bead15e2a5c84affa3ed296e2260404fd9d2ac2..bb6659a33e075db6cbdc2bb53fec60bd4bab95c1 100644
--- a/src/box/sysview_engine.cc
+++ b/src/box/sysview_engine.cc
@@ -33,29 +33,10 @@
 #include "schema.h"
 #include "space.h"
 
-static void
-sysview_replace(struct txn *txn, struct space *space,
-		struct tuple *old_tuple, struct tuple *new_tuple,
-		enum dup_replace_mode mode)
-{
-	(void) txn;
-	(void) space;
-	(void) old_tuple;
-	(void) new_tuple;
-	(void) mode;
-	tnt_raise(ClientError, ER_UNSUPPORTED, "sysview", "replace()");
-}
-
 struct SysviewSpace: public Handler {
-	SysviewSpace(Engine *e)
-		: Handler(e)
-	{
-		replace = sysview_replace;
-	}
+	SysviewSpace(Engine *e) : Handler(e) {}
 
-	virtual ~SysviewSpace()
-	{
-	}
+	virtual ~SysviewSpace() {}
 };
 
 SysviewEngine::SysviewEngine()
@@ -63,16 +44,6 @@ SysviewEngine::SysviewEngine()
 {
 }
 
-void
-SysviewEngine::recoverToCheckpoint(int64_t /* lsn */)
-{
-}
-
-void
-SysviewEngine::endRecovery()
-{
-}
-
 Handler *SysviewEngine::open()
 {
 	return new SysviewSpace(this);
@@ -101,59 +72,8 @@ SysviewEngine::createIndex(struct key_def *key_def)
 	}
 }
 
-void
-SysviewEngine::dropIndex(Index *index)
-{
-	(void) index;
-}
-
-void
-SysviewEngine::keydefCheck(struct space *space, struct key_def *key_def)
-{
-	(void) space;
-	(void) key_def;
-	/*
-	 * Don't bother checking key_def to match the view requirements.
-	 * Index::initIterator() must check key on each call.
-	 */
-}
-
-void
-SysviewEngine::beginJoin()
-{
-}
-
-int
-SysviewEngine::beginCheckpoint(int64_t lsn)
-{
-	(void) lsn;
-	return 0;
-}
-
-int
-SysviewEngine::waitCheckpoint()
-{
-	return 0;
-}
-
-void
-SysviewEngine::commitCheckpoint()
-{
-}
-
-void
-SysviewEngine::abortCheckpoint()
-{
-}
-
 bool
 SysviewEngine::needToBuildSecondaryKey(struct space * /* space */)
 {
 	return false;
 }
-
-void
-SysviewEngine::join(Relay *relay)
-{
-	(void) relay;
-}
diff --git a/src/box/sysview_engine.h b/src/box/sysview_engine.h
index 854e8d175249abd295313a36489b2d6494b1151f..d3b003282998459d4b2e467a7b6e66fe94c6bd9c 100644
--- a/src/box/sysview_engine.h
+++ b/src/box/sysview_engine.h
@@ -37,17 +37,7 @@ struct SysviewEngine: public Engine {
 	SysviewEngine();
 	virtual Handler *open();
 	virtual Index *createIndex(struct key_def *key_def);
-	virtual void dropIndex(Index *index);
-	virtual void keydefCheck(struct space *space, struct key_def *key_def);
-	virtual void beginJoin();
-	virtual void recoverToCheckpoint(int64_t lsn);
-	virtual void endRecovery();
 	virtual bool needToBuildSecondaryKey(struct space *space);
-	virtual void join(Relay*);
-	virtual int beginCheckpoint(int64_t);
-	virtual int waitCheckpoint();
-	virtual void commitCheckpoint();
-	virtual void abortCheckpoint();
 };
 
 #endif /* TARANTOOL_BOX_SYSVIEW_ENGINE_H_INCLUDED */
diff --git a/src/box/tuple.cc b/src/box/tuple.cc
index 25836fde3ca7d2daf4a2bb9743e9df65954b5a8f..6aa2f2df5ec7dfd81ee9141cca9e4fd639636aa2 100644
--- a/src/box/tuple.cc
+++ b/src/box/tuple.cc
@@ -365,11 +365,15 @@ tuple_next(struct tuple_iterator *it)
 extern inline uint32_t
 tuple_next_u32(struct tuple_iterator *it);
 
-static const char *
+const char *
 tuple_field_to_cstr(const char *field, uint32_t len)
 {
-	static __thread char buf[256];
-	len = MIN(len, sizeof(buf) - 1);
+	enum { MAX_STR_BUFS = 3, MAX_BUF_LEN = 256 };
+	static __thread char bufs[MAX_STR_BUFS][MAX_BUF_LEN];
+	static __thread int i = 0;
+	char *buf = bufs[i];
+	i = (i + 1) % MAX_STR_BUFS;
+	len = MIN(len, MAX_BUF_LEN - 1);
 	memcpy(buf, field, len);
 	buf[len] = '\0';
 	return buf;
diff --git a/src/box/tuple.h b/src/box/tuple.h
index 4f07f245a03b2fd339d0b648f204c52966bb51b8..98396201365543f3c9ed78658a66a7c9f6547828 100644
--- a/src/box/tuple.h
+++ b/src/box/tuple.h
@@ -143,8 +143,7 @@ tuple_format_ref(struct tuple_format *format, int count)
 };
 
 /**
- * An atom of Tarantool/Box storage. Consists of a list of fields.
- * The first field is always the primary key.
+ * An atom of Tarantool storage. Represents MsgPack Array.
  */
 struct tuple
 {
@@ -183,6 +182,8 @@ tuple_alloc(struct tuple_format *format, size_t size);
  * tuple->refs is 0.
  *
  * Throws an exception if tuple format is incorrect.
+ *
+ * \sa box_tuple_new()
  */
 struct tuple *
 tuple_new(struct tuple_format *format, const char *data, const char *end);
@@ -265,6 +266,40 @@ tuple_field_count(const struct tuple *tuple)
 	return mp_decode_array(&data);
 }
 
+/**
+ * @brief Validate a number of fields against format
+ */
+static inline void
+tuple_field_count_validate(struct tuple_format *format, const char *data)
+{
+	uint32_t field_count = mp_decode_array(&data);
+	if (unlikely(field_count < format->field_count))
+		tnt_raise(ClientError, ER_INDEX_FIELD_COUNT,
+			  (unsigned) field_count,
+			  (unsigned) format->field_count);
+}
+
+/**
+ * Get a field by id from an non-indexed tuple.
+ * Returns a pointer to BER-length prefixed field.
+ *
+ * @returns field data if field exists or NULL
+ */
+inline const char *
+tuple_field_raw(const char *data, uint32_t bsize, uint32_t i)
+{
+	const char *pos = data;
+	uint32_t size = mp_decode_array(&pos);
+	if (unlikely(i >= size))
+		return NULL;
+	for (uint32_t k = 0; k < i; k++) {
+		mp_next(&pos);
+	}
+	(void)bsize;
+	assert(pos <= data + bsize);
+	return pos;
+}
+
 /**
  * Get a field from tuple by index.
  * Returns a pointer to BER-length prefixed field.
@@ -291,18 +326,7 @@ tuple_field_old(const struct tuple_format *format,
 			return tuple->data + field_map[idx];
 		}
 	}
-
-	const char *pos = tuple->data;
-	uint32_t size = mp_decode_array(&pos);
-	if (unlikely(i >= size))
-		return NULL;
-
-	for (uint32_t k = 0; k < i; k++) {
-		mp_next(&pos);
-	}
-
-	assert(pos <= tuple->data + tuple->bsize);
-	return pos;
+	return tuple_field_raw(tuple->data, tuple->bsize, i);
 }
 
 /**
@@ -366,6 +390,10 @@ tuple_field_num(const struct tuple* tuple, uint32_t field_no)
 const char *
 tuple_field_cstr(struct tuple *tuple, uint32_t i);
 
+/** Helper method for the above function. */
+const char *
+tuple_field_to_cstr(const char *field, uint32_t len);
+
 /**
  * @brief Tuple Interator
  */
@@ -531,12 +559,7 @@ void
 tuple_to_obuf(struct tuple *tuple, struct obuf *buf);
 
 /**
- * Store tuple fields in the memory buffer.
- * \retval -1 on error.
- * \retval number of bytes written on success.
- * Upon successful return, the function returns the number of bytes written.
- * If buffer size is not enough then the return value is the number of bytes
- * which would have been written if enough space had been available.
+ * \copydoc box_tuple_to_buf()
  */
 ssize_t
 tuple_to_buf(const struct tuple *tuple, char *buf, size_t size);
@@ -559,52 +582,203 @@ tuple_end_snapshot();
 /** \cond public */
 typedef struct tuple_format box_tuple_format_t;
 
+/**
+ * Tuple Format.
+ *
+ * Each Tuple has associated format (class). Default format is used to
+ * create tuples which are not attach to any particular space.
+ */
 API_EXPORT box_tuple_format_t *
 box_tuple_format_default(void);
 
+/**
+ * Tuple
+ */
 typedef struct tuple box_tuple_t;
 
+/**
+ * Allocate and initialize a new tuple from a raw MsgPack Array data.
+ *
+ * \param format tuple format.
+ * Use box_tuple_format_default() to create space-independent tuple.
+ * \param data tuple data in MsgPack Array format ([field1, field2, ...]).
+ * \param end the end of \a data
+ * \retval NULL on out of memory
+ * \retval tuple otherwise
+ * \pre data, end is valid MsgPack Array
+ * \sa \code box.tuple.new(data) \endcode
+ */
 API_EXPORT box_tuple_t *
 box_tuple_new(box_tuple_format_t *format, const char *data, const char *end);
 
+/**
+ * Increase the reference counter of tuple.
+ *
+ * Tuples are reference counted. All functions that return tuples guarantee
+ * that the last returned tuple is refcounted internally until the next
+ * call to API function that yields or returns another tuple.
+ *
+ * You should increase the reference counter before taking tuples for long
+ * processing in your code. Such tuples will not be garbage collected even
+ * if another fiber remove they from space. After processing please
+ * decrement the reference counter using box_tuple_unref(), otherwise the
+ * tuple will leak.
+ *
+ * \param tuple a tuple
+ * \retval -1 on error (check box_error_last())
+ * \retval 0 on success
+ * \sa box_tuple_unref()
+ */
 API_EXPORT int
 box_tuple_ref(box_tuple_t *tuple);
 
+/**
+ * Decrease the reference counter of tuple.
+ *
+ * \param tuple a tuple
+ * \sa box_tuple_ref()
+ */
 API_EXPORT void
 box_tuple_unref(box_tuple_t *tuple);
 
+/**
+ * Return the number of fields in tuple (the size of MsgPack Array).
+ * \param tuple a tuple
+ */
 API_EXPORT uint32_t
 box_tuple_field_count(const box_tuple_t *tuple);
 
+/**
+ * Return the number of bytes used to store internal tuple data (MsgPack Array).
+ * \param tuple a tuple
+ */
 API_EXPORT size_t
 box_tuple_bsize(const box_tuple_t *tuple);
 
+/**
+ * Dump raw MsgPack data to the memory byffer \a buf of size \a size.
+ *
+ * Store tuple fields in the memory buffer.
+ * \retval -1 on error.
+ * \retval number of bytes written on success.
+ * Upon successful return, the function returns the number of bytes written.
+ * If buffer size is not enough then the return value is the number of bytes
+ * which would have been written if enough space had been available.
+ */
 API_EXPORT ssize_t
 box_tuple_to_buf(const box_tuple_t *tuple, char *buf, size_t size);
 
+/**
+ * Return the associated format.
+ * \param tuple tuple
+ * \return tuple_format
+ */
 API_EXPORT box_tuple_format_t *
 box_tuple_format(const box_tuple_t *tuple);
 
+/**
+ * Return the raw tuple field in MsgPack format.
+ *
+ * The buffer is valid until next call to box_tuple_* functions.
+ *
+ * \param tuple a tuple
+ * \param field_id zero-based index in MsgPack array.
+ * \retval NULL if i >= box_tuple_field_count(tuple)
+ * \retval msgpack otherwise
+ */
 API_EXPORT const char *
-box_tuple_field(const box_tuple_t *tuple, uint32_t i);
+box_tuple_field(const box_tuple_t *tuple, uint32_t field_id);
 
+/**
+ * Tuple iterator
+ */
 typedef struct tuple_iterator box_tuple_iterator_t;
 
+/**
+ * Allocate and initialize a new tuple iterator. The tuple iterator
+ * allow to iterate over fields at root level of MsgPack array.
+ *
+ * Example:
+ * \code
+ * box_tuple_iterator *it = box_tuple_iterator(tuple);
+ * if (it == NULL) {
+ *      // error handling using box_error_last()
+ * }
+ * const char *field;
+ * while (field = box_tuple_next(it)) {
+ *      // process raw MsgPack data
+ * }
+ *
+ * // rewind iterator to first position
+ * box_tuple_rewind(it);
+ * assert(box_tuple_position(it) == 0);
+ *
+ * // rewind iterator to first position
+ * field = box_tuple_seek(it, 3);
+ * assert(box_tuple_position(it) == 4);
+ *
+ * box_iterator_free(it);
+ * \endcode
+ *
+ * \post box_tuple_position(it) == 0
+ */
 API_EXPORT box_tuple_iterator_t *
 box_tuple_iterator(box_tuple_t *tuple);
 
+/**
+ * Destroy and free tuple iterator
+ */
 API_EXPORT void
 box_tuple_iterator_free(box_tuple_iterator_t *it);
 
+/**
+ * Return zero-based next position in iterator.
+ * That is, this function return the field id of field that will be
+ * returned by the next call to box_tuple_next(it). Returned value is zero
+ * after initialization or rewind and box_tuple_field_count(tuple)
+ * after the end of iteration.
+ *
+ * \param it tuple iterator
+ * \returns position.
+ */
 API_EXPORT uint32_t
 box_tuple_position(box_tuple_iterator_t *it);
 
+/**
+ * Rewind iterator to the initial position.
+ *
+ * \param it tuple iterator
+ * \post box_tuple_position(it) == 0
+ */
 API_EXPORT void
 box_tuple_rewind(box_tuple_iterator_t *it);
 
+/**
+ * Seek the tuple iterator.
+ *
+ * The returned buffer is valid until next call to box_tuple_* API.
+ * Requested field_no returned by next call to box_tuple_next(it).
+ *
+ * \param it tuple iterator
+ * \param field_no field no - zero-based position in MsgPack array.
+ * \post box_tuple_position(it) == field_no if returned value is not NULL
+ * \post box_tuple_position(it) == box_tuple_field_count(tuple) if returned
+ * value is NULL.
+ */
 API_EXPORT const char *
 box_tuple_seek(box_tuple_iterator_t *it, uint32_t field_no);
 
+/**
+ * Return the next tuple field from tuple iterator.
+ * The returned buffer is valid until next call to box_tuple_* API.
+ *
+ * \param it tuple iterator.
+ * \retval NULL if there are no more fields.
+ * \retval MsgPack otherwise
+ * \pre box_tuple_position(it) is zerod-based id of returned field
+ * \post box_tuple_position(it) == box_tuple_field_count(tuple) if returned
+ * value is NULL.
+ */
 API_EXPORT const char *
 box_tuple_next(box_tuple_iterator_t *it);
 
diff --git a/src/box/txn.cc b/src/box/txn.cc
index 83cfbcecbb9001da9fcab7cd6f567ec5fa51ea99..6c88ad545b961dc3f901a88d4ffc5575bd2b2725 100644
--- a/src/box/txn.cc
+++ b/src/box/txn.cc
@@ -28,19 +28,13 @@
  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
-#include "engine.h"
 #include "txn.h"
-#include "box.h"
+#include "engine.h"
+#include "box.h" /* global recovery */
 #include "tuple.h"
-#include "space.h"
-#include "main.h"
-#include "cluster.h"
 #include "recovery.h"
 #include <fiber.h>
 #include "request.h" /* for request_name */
-#include "session.h"
-#include "port.h"
-#include "iproto_constants.h"
 
 double too_long_threshold;
 
@@ -65,23 +59,6 @@ txn_add_redo(struct txn_stmt *stmt, struct request *request)
 	stmt->row = row;
 }
 
-void
-txn_replace(struct txn *txn, struct space *space,
-	    struct tuple *old_tuple, struct tuple *new_tuple,
-	    enum dup_replace_mode mode)
-{
-	assert(old_tuple || new_tuple);
-
-	space->handler->replace(txn, space, old_tuple, new_tuple, mode);
-
-	/*
-	 * Run on_replace triggers. For now, disallow mutation
-	 * of tuples in the trigger.
-	 */
-	if (! rlist_empty(&space->on_replace) && space->run_triggers)
-		trigger_run(&space->on_replace, txn);
-}
-
 /** Initialize a new stmt object within txn. */
 static struct txn_stmt *
 txn_stmt_new(struct txn *txn)
diff --git a/src/box/txn.h b/src/box/txn.h
index cc8f61aa603f918b8171a53f4ebc2738edd8b964..418415858b610467c8e721fd8f5d4ee6ae52bc6c 100644
--- a/src/box/txn.h
+++ b/src/box/txn.h
@@ -30,13 +30,12 @@
  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
-#include "index.h"
+#include "space.h"
 #include "trigger.h"
 #include "fiber.h"
 
 extern double too_long_threshold;
 struct tuple;
-struct space;
 
 /**
  * A single statement of a multi-statement
@@ -121,6 +120,16 @@ txn_begin_stmt(struct request *request, struct space *space);
 static inline void
 txn_commit_stmt(struct txn *txn)
 {
+	/*
+	 * Run on_replace triggers. For now, disallow mutation
+	 * of tuples in the trigger.
+	 */
+	struct txn_stmt *stmt = txn->stmt;
+	if (stmt->row && stmt->space && !rlist_empty(&stmt->space->on_replace)
+	    && stmt->space->run_triggers) {
+
+		trigger_run(&stmt->space->on_replace, txn);
+	}
 	if (txn->autocommit)
 		txn_commit(txn);
 	else
@@ -142,11 +151,6 @@ txn_rollback_stmt();
 void
 txn_check_autocommit(struct txn *txn, const char *where);
 
-void
-txn_replace(struct txn *txn, struct space *space,
-	    struct tuple *old_tuple, struct tuple *new_tuple,
-	    enum dup_replace_mode mode);
-
 /** Last statement of the transaction. */
 static inline struct txn_stmt *
 txn_stmt(struct txn *txn)
@@ -162,6 +166,11 @@ txn_stmt(struct txn *txn)
 /** \cond public */
 
 /**
+ * Begin a transaction in the current fiber.
+ *
+ * A transaction is attached to caller fiber, therefore one fiber can have
+ * only one active transaction.
+ *
  * @retval 0 - success
  * @retval -1 - failed, perhaps a transaction has already been
  * started
@@ -169,9 +178,18 @@ txn_stmt(struct txn *txn)
 API_EXPORT int
 box_txn_begin(void);
 
+/**
+ * Commit the current transaction.
+ * @retval 0 - success
+ * @retval -1 - failed, perhaps a disk write failure.
+ * started
+ */
 API_EXPORT int
 box_txn_commit(void);
 
+/**
+ * Rollback the current transaction.
+ */
 API_EXPORT void
 box_txn_rollback(void);
 
@@ -180,7 +198,7 @@ box_txn_rollback(void);
  * The memory is automatically deallocated when the transaction
  * is committed or rolled back.
  *
- * @retval 0  out of memory
+ * @retval NULL out of memory
  */
 API_EXPORT void *
 box_txn_alloc(size_t size);
diff --git a/src/box/user.cc b/src/box/user.cc
index 2e028c575dbd3dad871b203996adb5c467b01400..54670c318fe14bc9667a2ac359e4af28909c702e 100644
--- a/src/box/user.cc
+++ b/src/box/user.cc
@@ -33,6 +33,7 @@
 #include "assoc.h"
 #include "schema.h"
 #include "space.h"
+#include "memtx_index.h"
 #include "func.h"
 #include "index.h"
 #include "bit/bit.h"
@@ -279,12 +280,11 @@ user_reload_privs(struct user *user)
 		struct space *space = space_cache_find(BOX_PRIV_ID);
 		char key[6];
 		/** Primary key - by user id */
-		Index *index = index_find(space, 0);
+		MemtxIndex *index = index_find_system(space, 0);
 		mp_encode_uint(key, user->uid);
 
 		struct iterator *it = index->position();
 		index->initIterator(it, ITER_EQ, key, 1);
-		IteratorGuard it_guard(it);
 
 		struct tuple *tuple;
 		while ((tuple = it->next(it))) {
diff --git a/src/cbus.cc b/src/cbus.cc
index 131a7f9d57c4400423d5d934d71bb7daae0c5f49..fd4a3f002ddc8615e47a425aec3277f2b9b372fd 100644
--- a/src/cbus.cc
+++ b/src/cbus.cc
@@ -31,6 +31,14 @@
 #include "cbus.h"
 #include "scoped_guard.h"
 
+struct rmean *rmean_net = NULL;
+const char *rmean_net_strings[RMEAN_NET_LAST] = {
+	"EVENTS",
+	"LOCKS",
+	"RECEIVED",
+	"SENT"
+};
+
 static void
 cbus_flush_cb(ev_loop * /* loop */, struct ev_async *watcher,
 	      int /* events */);
@@ -183,9 +191,14 @@ cbus_flush_cb(ev_loop * /* loop */, struct ev_async *watcher,
 	STAILQ_CONCAT(&peer->output, &peer->pipe);
 	cbus_unlock(pipe->bus);
 
+
 	pipe->n_input = 0;
-	if (pipe_was_empty)
+	if (pipe_was_empty) {
+		/* Count statistics */
+		rmean_collect(rmean_net, RMEAN_NET_EVENTS, 1);
+
 		ev_async_send(pipe->consumer, &pipe->fetch_output);
+	}
 	if (peer_output_was_empty && !STAILQ_EMPTY(&peer->output))
 		ev_feed_event(peer->consumer, &peer->fetch_output, EV_CUSTOM);
 }
@@ -200,6 +213,8 @@ cpipe_peek_impl(struct cpipe *pipe)
 	assert(peer->producer == loop());
 
 	bool peer_pipe_was_empty = false;
+
+
 	cbus_lock(pipe->bus);
 	STAILQ_CONCAT(&pipe->output, &pipe->pipe);
 	if (! STAILQ_EMPTY(&peer->input)) {
@@ -209,8 +224,12 @@ cpipe_peek_impl(struct cpipe *pipe)
 	cbus_unlock(pipe->bus);
 	peer->n_input = 0;
 
-	if (peer_pipe_was_empty)
+	if (peer_pipe_was_empty) {
+		/* Count statistics */
+		rmean_collect(rmean_net, RMEAN_NET_EVENTS, 1);
+
 		ev_async_send(peer->consumer, &peer->fetch_output);
+	}
 	return STAILQ_FIRST(&pipe->output);
 }
 
diff --git a/src/cbus.h b/src/cbus.h
index 4985a36e3e1b15cc9d28d6d339846b22e52c9464..3b717d369950e79ea007dd7211e3a98b420ab343 100644
--- a/src/cbus.h
+++ b/src/cbus.h
@@ -32,6 +32,7 @@
  */
 #include "fiber.h"
 #include "coio.h"
+#include "rmean.h"
 
 /** cbus, cmsg - inter-cord bus and messaging */
 
@@ -39,6 +40,20 @@ struct cmsg;
 struct cpipe;
 typedef void (*cmsg_f)(struct cmsg *);
 
+/** rmean_net - network statistics (iproto & cbus) */
+extern struct rmean *rmean_net;
+
+enum rmean_net_name {
+	RMEAN_NET_EVENTS,
+	RMEAN_NET_LOCKS,
+	RMEAN_NET_RECEIVED,
+	RMEAN_NET_SENT,
+
+	RMEAN_NET_LAST
+};
+
+extern const char *rmean_net_strings[RMEAN_NET_LAST];
+
 /**
  * One hop in a message travel route.  A message may need to be
  * delivered to many destinations before it can be dispensed with.
@@ -364,6 +379,10 @@ cbus_join(struct cbus *bus, struct cpipe *pipe);
 static inline void
 cbus_lock(struct cbus *bus)
 {
+	/* Count statistics */
+	if (rmean_net)
+		rmean_collect(rmean_net, RMEAN_NET_LOCKS, 1);
+
 	tt_pthread_mutex_lock(&bus->mutex);
 }
 
diff --git a/src/coeio.cc b/src/coeio.cc
index c15cfabf232a97112f512dc7fe7e36862a42a174..cc01d906373394910d9d3a8ee6a0fd10b4530651 100644
--- a/src/coeio.cc
+++ b/src/coeio.cc
@@ -187,31 +187,6 @@ coio_on_call(eio_req *req)
 	req->result = task->call_cb(task->ap);
 }
 
-/**
- * Create new eio task with specified function and
- * arguments. Yield and wait until the task is complete
- * or a timeout occurs.
- *
- * This function doesn't throw exceptions to avoid double error
- * checking: in most cases it's also necessary to check the return
- * value of the called function and perform necessary actions. If
- * func sets errno, the errno is preserved across the call.
- *
- * @retval -1 and errno = ENOMEM if failed to create a task
- * @retval the function return (errno is preserved).
- *
- * @code
- *	static ssize_t openfile_cb(va_list ap)
- *	{
- *	         const char *filename = va_arg(ap);
- *	         int flags = va_arg(ap);
- *	         return open(filename, flags);
- *	}
- *
- *	 if (coio_call(openfile_cb, 0.10, "/tmp/file", 0) == -1)
- *		// handle errors.
- *	...
- */
 ssize_t
 coio_call(ssize_t (*func)(va_list ap), ...)
 {
@@ -235,7 +210,9 @@ coio_call(ssize_t (*func)(va_list ap), ...)
 	eio_submit(&task->base);
 
 	fiber_yield();
-	assert(task->complete);
+	/* Spurious wakeup indicates a severe BUG, fail early. */
+	if (task->complete == 0)
+		panic("Wrong fiber woken");
 	va_end(task->ap);
 
 	fiber_set_cancellable(cancellable);
@@ -347,27 +324,3 @@ coio_getaddrinfo(const char *host, const char *port,
 	return rc;
 }
 
-static ssize_t
-cord_cojoin_cb(va_list ap)
-{
-	struct cord *cord = va_arg(ap, struct cord *);
-	void *retval = NULL;
-	int res = tt_pthread_join(cord->id, &retval);
-	return res;
-}
-
-int
-cord_cojoin(struct cord *cord)
-{
-	assert(cord() != cord); /* Can't join self. */
-	int rc = coio_call(cord_cojoin_cb, cord);
-	if (rc == 0 && !diag_is_empty(&cord->fiber->diag)) {
-		diag_move(&cord->fiber->diag, &fiber()->diag);
-		cord_destroy(cord);
-		 /* re-throw exception in this fiber */
-		diag_last_error(&fiber()->diag)->raise();
-	}
-	cord_destroy(cord);
-	return rc;
-}
-
diff --git a/src/coeio.h b/src/coeio.h
index 1523f4aac9ea9a61aca69c168aa68b09c400e2ac..374d6db4c5cb1080e64c84985ec697c8712125b2 100644
--- a/src/coeio.h
+++ b/src/coeio.h
@@ -84,11 +84,42 @@ coio_task(struct coio_task *task, coio_task_cb func,
 	  coio_task_timeout_cb on_timeout, double timeout);
 
 /** \cond public */
+
+/**
+ * Create new eio task with specified function and
+ * arguments. Yield and wait until the task is complete
+ * or a timeout occurs.
+ *
+ * This function doesn't throw exceptions to avoid double error
+ * checking: in most cases it's also necessary to check the return
+ * value of the called function and perform necessary actions. If
+ * func sets errno, the errno is preserved across the call.
+ *
+ * @retval -1 and errno = ENOMEM if failed to create a task
+ * @retval the function return (errno is preserved).
+ *
+ * @code
+ *	static ssize_t openfile_cb(va_list ap)
+ *	{
+ *	         const char *filename = va_arg(ap);
+ *	         int flags = va_arg(ap);
+ *	         return open(filename, flags);
+ *	}
+ *
+ *	if (coio_call(openfile_cb, 0.10, "/tmp/file", 0) == -1)
+ *		// handle errors.
+ *	...
+ * @endcode
+ */
 ssize_t
 coio_call(ssize_t (*func)(va_list ap), ...);
 
 struct addrinfo;
 
+/**
+ * Fiber-friendly version of getaddrinfo(3).
+ * \sa getaddrinfo().
+ */
 int
 coio_getaddrinfo(const char *host, const char *port,
 		 const struct addrinfo *hints, struct addrinfo **res,
diff --git a/src/ffisyms.cc b/src/ffisyms.cc
index f385ddbc7d4b78cee5f68f6c262488f0a0cfe0bd..961c3d1699fc5e83141b171990a29119f303dc0c 100644
--- a/src/ffisyms.cc
+++ b/src/ffisyms.cc
@@ -76,7 +76,6 @@ void *ffi_symbols[] = {
 	(void *) random_bytes,
 	(void *) fiber_time,
 	(void *) fiber_time64,
-	(void *) sophia_schedule,
 	(void *) tarantool_lua_slab_cache,
 	(void *) ibuf_create,
 	(void *) ibuf_destroy,
diff --git a/src/fiber.cc b/src/fiber.cc
index 80e3f3a7e020a3602d296bb768b8077569afd231..f5eb321478e8f286683ccef80623a417769a0c40 100644
--- a/src/fiber.cc
+++ b/src/fiber.cc
@@ -38,6 +38,25 @@
 #include "assoc.h"
 #include "memory.h"
 #include "trigger.h"
+#include "third_party/pmatomic.h"
+
+/*
+ * Defines a handler to be executed on exit from cord's thread func,
+ * accessible via cord()->on_exit (normally NULL). It is used to
+ * implement cord_cojoin.
+ */
+struct cord_on_exit {
+	void (*callback)(void*);
+	void *argument;
+};
+
+/*
+ * A special value distinct from any valid pointer to cord_on_exit
+ * structure AND NULL. This value is stored in cord()->on_exit by the
+ * thread function prior to thread termination.
+ */
+static const struct cord_on_exit cord_on_exit_sentinel = { NULL, NULL };
+#define CORD_ON_EXIT_WONT_RUN (&cord_on_exit_sentinel)
 
 static struct cord main_cord;
 __thread struct cord *cord_ptr = NULL;
@@ -548,6 +567,7 @@ void
 cord_create(struct cord *cord, const char *name)
 {
 	cord->id = pthread_self();
+	cord->on_exit = NULL;
 	cord->loop = cord->id == main_thread_id ?
 		ev_default_loop(EVFLAG_AUTO) : ev_loop_new(EVFLAG_AUTO);
 	slab_cache_create(&cord->slabc, &runtime);
@@ -631,10 +651,26 @@ void *cord_thread_func(void *p)
 		 */
 		res = NULL;
 	}
+	/*
+	 * cord()->on_exit initially holds NULL. This field is
+	 * change-once.
+	 * Either handler installation succeeds (in cord_cojoin())
+	 * or prior to thread exit the thread function discovers
+	 * that no handler was installed so far and it stores
+	 * CORD_ON_EXIT_WONT_RUN to prevent a future handler
+	 * installation (since a handler won't run anyway).
+	 */
+	const struct cord_on_exit *handler = NULL; /* expected value */
+	bool changed;
+
+	changed = pm_atomic_compare_exchange_strong(&cord()->on_exit,
+	                                            &handler,
+	                                            CORD_ON_EXIT_WONT_RUN);
+	if (!changed)
+		handler->callback(handler->argument);
 	return res;
 }
 
-
 int
 cord_start(struct cord *cord, const char *name, void *(*f)(void *), void *arg)
 {
@@ -675,6 +711,94 @@ cord_join(struct cord *cord)
 	return res;
 }
 
+/** The state of the waiter for a thread to complete. */
+struct cord_cojoin_ctx
+{
+	struct ev_loop *loop;
+	/** Waiting fiber. */
+	struct fiber *fiber;
+	/*
+	 * This event is signalled when the subject thread is
+	 * about to die.
+	 */
+	struct ev_async async;
+	bool task_complete;
+};
+
+static void
+cord_cojoin_on_exit(void *arg)
+{
+	struct cord_cojoin_ctx *ctx = (struct cord_cojoin_ctx *)arg;
+
+	ev_async_send(ctx->loop, &ctx->async);
+}
+
+static void
+cord_cojoin_wakeup(struct ev_loop *loop, struct ev_async *ev, int revents)
+{
+	(void)loop;
+	(void)revents;
+
+	struct cord_cojoin_ctx *ctx = (struct cord_cojoin_ctx *)ev->data;
+
+	ctx->task_complete = true;
+	fiber_wakeup(ctx->fiber);
+}
+
+int
+cord_cojoin(struct cord *cord)
+{
+	assert(cord() != cord); /* Can't join self. */
+
+	struct cord_cojoin_ctx ctx;
+	ctx.loop = loop();
+	ctx.fiber = fiber();
+	ctx.task_complete = false;
+
+	ev_async_init(&ctx.async, cord_cojoin_wakeup);
+	ctx.async.data = &ctx;
+	ev_async_start(loop(), &ctx.async);
+
+	struct cord_on_exit handler = { cord_cojoin_on_exit, &ctx };
+
+	/*
+	 * cord->on_exit initially holds a NULL value. This field is
+	 * change-once.
+	 */
+	const struct cord_on_exit *prev_handler = NULL; /* expected value */
+	bool changed = pm_atomic_compare_exchange_strong(&cord->on_exit,
+	                                                 &prev_handler,
+	                                                 &handler);
+	/*
+	 * A handler installation fails either if the thread did exit or
+	 * if someone is already joining this cord (BUG).
+	 */
+	if (!changed) {
+		/* Assume cord's thread already exited. */
+		assert(prev_handler == CORD_ON_EXIT_WONT_RUN);
+	} else {
+		/*
+		 * Wait until the thread exits. Prior to exit the
+		 * thread invokes cord_cojoin_on_exit, signaling
+		 * ev_async, making the event loop call
+		 * cord_cojoin_wakeup, waking up this fiber again.
+		 *
+		 * The fiber is non-cancellable during the wait to
+		 * avoid invalidating of the cord_cojoin_ctx
+		 * object declared on stack.
+		 */
+		bool cancellable = fiber_set_cancellable(false);
+		fiber_yield();
+		/* Spurious wakeup indicates a severe BUG, fail early. */
+		if (ctx.task_complete == 0)
+			panic("Wrong fiber woken");
+		fiber_set_cancellable(cancellable);
+	}
+
+	ev_async_stop(loop(), &ctx.async);
+	return cord_join(cord);
+}
+
 void
 break_ev_loop_f(struct trigger * /* trigger */, void * /* event */)
 {
diff --git a/src/fiber.h b/src/fiber.h
index 649ede3de9a2f02f8abfa7aa8b397dcdea180228..17a6acbf326e6c6f80dd0ffc39a7e871cf787a4c 100644
--- a/src/fiber.h
+++ b/src/fiber.h
@@ -113,7 +113,8 @@ enum fiber_key {
 	FIBER_KEY_TXN = 2,
 	/** User global privilege and authentication token */
 	FIBER_KEY_USER = 3,
-	FIBER_KEY_MAX = 4
+	FIBER_RESULT = 4,
+	FIBER_KEY_MAX = 5
 };
 
 typedef void(*fiber_func)(va_list);
@@ -166,6 +167,8 @@ struct fiber {
 
 enum { FIBER_CALL_STACK = 16 };
 
+struct cord_on_exit;
+
 /**
  * @brief An independent execution unit that can be managed by a separate OS
  * thread. Each cord consists of fibers to implement cooperative multitasking
@@ -183,6 +186,7 @@ struct cord {
          */
 	uint32_t max_fid;
 	pthread_t id;
+	const struct cord_on_exit *on_exit;
 	/** A helper hash to map id -> fiber. */
 	struct mh_i32ptr_t *fiber_registry;
 	/** All fibers */
diff --git a/src/latch.h b/src/latch.h
index 449ce3e4b8d730c6270b9f1b22c51b9714231d5b..7b392020a111b86bf20ae7f941046cb793d56958 100644
--- a/src/latch.h
+++ b/src/latch.h
@@ -120,8 +120,7 @@ latch_lock_timeout(struct latch *l, ev_tstamp timeout)
 }
 
 /**
- * Lock a latch (no timeout). Waits indefinitely until
- * the current fiber can gain access to the latch.
+ * \copydoc box_latch_lock
  */
 static inline void
 latch_lock(struct latch *l)
@@ -130,9 +129,7 @@ latch_lock(struct latch *l)
 }
 
 /**
- * Try to lock a latch. Return immediately if the latch is locked.
- * @retval 0 - success
- * @retval 1 - the latch is locked.
+ * \copydoc box_latch_trylock
  */
 static inline int
 latch_trylock(struct latch *l)
@@ -141,8 +138,7 @@ latch_trylock(struct latch *l)
 }
 
 /**
- * Unlock a latch. The fiber calling this function must
- * own the latch.
+ * \copydoc box_latch_unlock
  */
 static inline void
 latch_unlock(struct latch *l)
@@ -157,26 +153,52 @@ latch_unlock(struct latch *l)
 }
 
 /** \cond public */
+
 /**
- * API of C stored function.
+ * A lock for cooperative multitasking environment
  */
-
 typedef struct box_latch box_latch_t;
 
+/**
+ * Allocate and initialize the new latch.
+ * \returns latch
+ */
 API_EXPORT box_latch_t*
 box_latch_new(void);
 
+/**
+ * Destroy and free the latch.
+ * \param latch latch
+ */
 API_EXPORT void
-box_latch_delete(box_latch_t* bl);
+box_latch_delete(box_latch_t *latch);
 
+/**
+* Lock a latch. Waits indefinitely until the current fiber can gain access to
+* the latch.
+*
+* \param latch a latch
+*/
 API_EXPORT void
-box_latch_lock(box_latch_t* bl);
+box_latch_lock(box_latch_t *latch);
 
+/**
+ * Try to lock a latch. Return immediately if the latch is locked.
+ * \param latch a latch
+ * \retval 0 - success
+ * \retval 1 - the latch is locked.
+ */
 API_EXPORT int
-box_latch_trylock(box_latch_t* bl);
+box_latch_trylock(box_latch_t *latch);
 
+/**
+ * Unlock a latch. The fiber calling this function must
+ * own the latch.
+ *
+ * \param latch a ltach
+ */
 API_EXPORT void
-box_latch_unlock(box_latch_t* bl);
+box_latch_unlock(box_latch_t *latch);
 
 /** \endcond public */
 
diff --git a/src/lib/csv/csv.c b/src/lib/csv/csv.c
index d4bf2ea1696be4a0a1ccc607f4f6d9cdc4e355af..a6cdb9fbcee77106adda7ddaa759e1e3ff2a7adb 100644
--- a/src/lib/csv/csv.c
+++ b/src/lib/csv/csv.c
@@ -350,6 +350,7 @@ csv_escape_field(struct csv *csv, const char *field,
 		 size_t field_len, char *dst, size_t buf_size)
 {
 	char *p = dst;
+	(void) buf_size;
 	int inquotes = 0;
 	/* surround quotes, only if there is delimiter \n or \r */
 	if (memchr(field, csv->delimiter, field_len) ||
diff --git a/src/lib/salad/rtree.c b/src/lib/salad/rtree.c
index 5901168ab515058e2f912e93eabebb1ece3790b4..af6c86968a339900b1023c799ad9736b95fcf15f 100644
--- a/src/lib/salad/rtree.c
+++ b/src/lib/salad/rtree.c
@@ -131,6 +131,28 @@ rtree_set2dp(struct rtree_rect *rect, coord_t x, coord_t y)
 	rect->coords[3] = y;
 }
 
+/* Manhattan distance */
+static sq_coord_t
+rtree_rect_neigh_distance(const struct rtree_rect *rect,
+			   const struct rtree_rect *neigh_rect,
+			   unsigned dimension)
+{
+	sq_coord_t result = 0;
+	for (int i = dimension; --i >= 0; ) {
+		const coord_t *coords = &rect->coords[2 * i];
+		coord_t neigh_coord = neigh_rect->coords[2 * i];
+		if (neigh_coord < coords[0]) {
+			sq_coord_t diff = (sq_coord_t)(neigh_coord - coords[0]);
+			result += -diff;
+		} else if (neigh_coord > coords[1]) {
+			sq_coord_t diff = (sq_coord_t)(neigh_coord - coords[1]);
+			result += diff;
+		}
+	}
+	return result;
+}
+
+/* Euclid distance, squared */
 static sq_coord_t
 rtree_rect_neigh_distance2(const struct rtree_rect *rect,
 			   const struct rtree_rect *neigh_rect,
@@ -855,8 +877,13 @@ rtree_iterator_process_neigh(struct rtree_iterator *itr,
 	for (int i = 0, n = pg->n; i < n; i++) {
 		struct rtree_page_branch *b;
 		b = rtree_branch_get(itr->tree, pg, i);
-		coord_t distance =
-			rtree_rect_neigh_distance2(&b->rect, &itr->rect, d);
+		coord_t distance;
+		if (itr->tree->distance_type == RTREE_EUCLID)
+			distance = rtree_rect_neigh_distance2(&b->rect,
+							      &itr->rect, d);
+		else
+			distance = rtree_rect_neigh_distance(&b->rect,
+							     &itr->rect, d);
 		struct rtree_neighbor *neigh =
 			rtree_iterator_new_neighbor(itr, b->data.page,
 						    distance, level - 1);
@@ -919,7 +946,8 @@ rtree_iterator_next(struct rtree_iterator *itr)
 
 int
 rtree_init(struct rtree *tree, unsigned dimension, uint32_t extent_size,
-	   rtree_extent_alloc_t extent_alloc, rtree_extent_free_t extent_free)
+	   rtree_extent_alloc_t extent_alloc, rtree_extent_free_t extent_free,
+	   enum rtree_distance_type distance_type)
 {
 	tree->n_records = 0;
 	tree->height = 0;
@@ -929,6 +957,7 @@ rtree_init(struct rtree *tree, unsigned dimension, uint32_t extent_size,
 	tree->free_pages = 0;
 
 	tree->dimension = dimension;
+	tree->distance_type = distance_type;
 	tree->page_branch_size =
 		(RTREE_BRANCH_DATA_SIZE + dimension * 2 * sizeof(coord_t));
 	tree->page_size = RTREE_OPTIMAL_BRANCHES_IN_PAGE *
@@ -1071,9 +1100,15 @@ rtree_search(const struct rtree *tree, const struct rtree_rect *rect,
 		if (tree->root) {
 			struct rtree_rect cover;
 			rtree_page_cover(tree, tree->root, &cover);
-			sq_coord_t distance =
+			sq_coord_t distance;
+			if (tree->distance_type == RTREE_EUCLID)
+				distance =
 				rtree_rect_neigh_distance2(&cover, rect,
 							   tree->dimension);
+			else
+				distance =
+				rtree_rect_neigh_distance(&cover, rect,
+							  tree->dimension);
 			struct rtree_neighbor *n =
 				rtree_iterator_new_neighbor(itr, tree->root,
 							    distance,
diff --git a/src/lib/salad/rtree.h b/src/lib/salad/rtree.h
index 4f5fdc8d1ce7cad279a140f320038c3c7804457d..72a87d37b062b2a71e54521862ce2d900656574c 100644
--- a/src/lib/salad/rtree.h
+++ b/src/lib/salad/rtree.h
@@ -114,6 +114,12 @@ typedef bool (*rtree_comparator_t)(const struct rtree_rect *rt1,
 				   const struct rtree_rect *rt2,
 				   unsigned dimension);
 
+/* Type distance comparison */
+enum rtree_distance_type {
+	RTREE_EUCLID = 0, /* Euclid distance, sqrt(dx*dx + dy*dy) */
+	RTREE_MANHATTAN = 1 /* Manhattan distance, fabs(dx) + fabs(dy) */
+};
+
 /* Main rtree struct */
 struct rtree
 {
@@ -144,6 +150,8 @@ struct rtree
 	struct matras mtab;
 	/* List of free pages */
 	void *free_pages;
+	/* Distance type */
+	enum rtree_distance_type distance_type;
 };
 
 /* Struct for iteration and retrieving rtree values */
@@ -227,7 +235,8 @@ rtree_set2dp(struct rtree_rect *rect, coord_t x, coord_t y);
  */
 int
 rtree_init(struct rtree *tree, unsigned dimension, uint32_t extent_size,
-	   rtree_extent_alloc_t extent_alloc, rtree_extent_free_t extent_free);
+	   rtree_extent_alloc_t extent_alloc, rtree_extent_free_t extent_free,
+	   enum rtree_distance_type distance_type);
 
 /**
  * @brief Destroy a tree
diff --git a/src/lib/small/lf_lifo.h b/src/lib/small/lf_lifo.h
index c359202cd1f63f0706315fc91baaa8b11a118177..9f827874c3203bdc73bef95cb4857ba372f54c08 100644
--- a/src/lib/small/lf_lifo.h
+++ b/src/lib/small/lf_lifo.h
@@ -34,18 +34,12 @@
 #include <stdint.h>
 #include <stdbool.h>
 #include <stdlib.h>
+#include "third_party/pmatomic.h"
 
 #if defined(__cplusplus)
 extern "C" {
 #endif /* defined(__cplusplus) */
 
-/**
- * Provide wrappers around gcc built-ins for now.
- * These built-ins work with all numeric types - may not
- * be the case when another implementation is used.
- */
-#define atomic_cas(a, b, c) __sync_val_compare_and_swap(a, b, c)
-
 /**
  * A very primitive implementation of lock-free
  * LIFO (last in first out, AKA stack, AKA single-linked
@@ -90,7 +84,7 @@ lf_lifo_push(struct lf_lifo *head, void *elem)
 		 * coerce to unsigned short
 		 */
 		void *newhead = (char *) elem + aba_value((char *) tail + 1);
-		if (atomic_cas(&head->next, tail, newhead) == tail)
+		if (pm_atomic_compare_exchange_strong(&head->next, &tail, newhead))
 			return head;
 	} while (true);
 }
@@ -112,7 +106,7 @@ lf_lifo_pop(struct lf_lifo *head)
 		 */
 		void *newhead = ((char *) lf_lifo(elem->next) +
 				 aba_value(tail));
-		if (atomic_cas(&head->next, tail, newhead) == tail)
+		if (pm_atomic_compare_exchange_strong(&head->next, &tail, newhead))
 			return elem;
 	} while (true);
 }
diff --git a/src/lib/small/quota.h b/src/lib/small/quota.h
index 31e5848065392119e605d8013577d317c8a78f6f..ebe2ec9d002b98018bbaf97573be240788bdbd98 100644
--- a/src/lib/small/quota.h
+++ b/src/lib/small/quota.h
@@ -35,6 +35,7 @@
 #include <stdint.h>
 #include <assert.h>
 #include <unistd.h>
+#include "third_party/pmatomic.h"
 
 #if defined(__cplusplus)
 extern "C" {
@@ -67,14 +68,6 @@ quota_init(struct quota *quota, size_t total)
 	quota->value = new_total << 32;
 }
 
-/**
- * Provide wrappers around gcc built-ins for now.
- * These built-ins work with all numeric types - may not
- * be the case when another implementation is used.
- * Private use only.
- */
-#define atomic_cas(a, b, c) __sync_val_compare_and_swap(a, b, c)
-
 /**
  * Get current quota limit
  */
@@ -121,7 +114,7 @@ quota_set(struct quota *quota, size_t new_total)
 			return  -1;
 		uint64_t new_value =
 			((uint64_t) new_total_in_units << 32) | used_in_units;
-		if (atomic_cas(&quota->value, value, new_value) == value)
+		if (pm_atomic_compare_exchange_strong(&quota->value, &value, new_value))
 			break;
 	}
 	return new_total_in_units * QUOTA_UNIT_SIZE;
@@ -153,7 +146,7 @@ quota_use(struct quota *quota, size_t size)
 		uint64_t new_value =
 			((uint64_t) total_in_units << 32) | new_used_in_units;
 
-		if (atomic_cas(&quota->value, value, new_value) == value)
+		if (pm_atomic_compare_exchange_strong(&quota->value, &value, new_value))
 			break;
 	}
 	return size_in_units * QUOTA_UNIT_SIZE;
@@ -178,12 +171,11 @@ quota_release(struct quota *quota, size_t size)
 		uint64_t new_value =
 			((uint64_t) total_in_units << 32) | new_used_in_units;
 
-		if (atomic_cas(&quota->value, value, new_value) == value)
+		if (pm_atomic_compare_exchange_strong(&quota->value, &value, new_value))
 			break;
 	}
 }
 
-#undef atomic_cas
 #undef QUOTA_UNIT_SIZE
 
 #if defined(__cplusplus)
diff --git a/src/lib/small/slab_arena.c b/src/lib/small/slab_arena.c
index 8d108f629ac2af40a01157a72981aa78e50b28f1..51b810edf1b9b5bfb7c0251f8135df4515b7ed71 100644
--- a/src/lib/small/slab_arena.c
+++ b/src/lib/small/slab_arena.c
@@ -37,17 +37,19 @@
 #include <stdbool.h>
 #include <assert.h>
 #include <limits.h>
+#include "third_party/pmatomic.h"
 
 #if !defined(MAP_ANONYMOUS)
 #define MAP_ANONYMOUS MAP_ANON
 #endif
 
-void
+static void
 munmap_checked(void *addr, size_t size)
 {
 	if (munmap(addr, size)) {
 		char buf[64];
-		intptr_t ignore_it = (intptr_t)strerror_r(errno, buf, sizeof(buf));
+		intptr_t ignore_it = (intptr_t)strerror_r(errno, buf,
+							  sizeof(buf));
 		(void)ignore_it;
 		fprintf(stderr, "Error in munmap(%p, %zu): %s\n",
 			addr, size, buf);
@@ -76,7 +78,7 @@ mmap_checked(size_t size, size_t align, int flags)
 	munmap_checked(map, size);
 
 	/*
-	 * mmap twice the requested amount to be able to align
+	 * mmap enough amount to be able to align
 	 * the mapped address.  This can lead to virtual memory
 	 * fragmentation depending on the kernels allocation
 	 * strategy.
@@ -183,12 +185,18 @@ slab_map(struct slab_arena *arena)
 		return NULL;
 
 	/** Need to allocate a new slab. */
-	size_t used = __sync_add_and_fetch(&arena->used, arena->slab_size);
+	size_t used = pm_atomic_fetch_add(&arena->used, arena->slab_size);
+	used += arena->slab_size;
 	if (used <= arena->prealloc)
 		return arena->arena + used - arena->slab_size;
 
-	return mmap_checked(arena->slab_size, arena->slab_size,
-			    arena->flags);
+	ptr = mmap_checked(arena->slab_size, arena->slab_size,
+			   arena->flags);
+	if (!ptr) {
+		__sync_sub_and_fetch(&arena->used, arena->slab_size);
+		quota_release(arena->quota, arena->slab_size);
+	}
+	return ptr;
 }
 
 void
diff --git a/src/lib/small/slab_arena.h b/src/lib/small/slab_arena.h
index 308fa26b89784c6709e17b64b49eff61edacb520..86c7bd107e89e90a03c3244e70fa68a11cb4c3fa 100644
--- a/src/lib/small/slab_arena.h
+++ b/src/lib/small/slab_arena.h
@@ -68,9 +68,8 @@ struct slab_arena {
 	 */
 	size_t prealloc;
 	/**
-	 * How much memory in the preallocated arena has
+	 * How much memory in the arena has
 	 * already been initialized for slabs.
-	 * @invariant used <= prealloc.
 	 */
 	size_t used;
 	/**
diff --git a/src/lib/small/slab_cache.c b/src/lib/small/slab_cache.c
index aad3a3412f320870c4f9729ffaa5d6247142d641..44a79f3358dcf48077b257b8dd64bd3a26e6c9fb 100644
--- a/src/lib/small/slab_cache.c
+++ b/src/lib/small/slab_cache.c
@@ -109,9 +109,12 @@ slab_is_free(struct slab *slab)
 static inline void
 slab_poison(struct slab *slab)
 {
-	static const char poison_char = 'P';
+	(void)slab;
+#ifndef NDEBUG
+	const char poison_char = 'P';
 	memset((char *) slab + slab_sizeof(), poison_char,
 	       slab->size - slab_sizeof());
+#endif
 }
 
 static inline void
diff --git a/src/lua/init.cc b/src/lua/init.cc
index 31c7f3a6c19094d50cafb9566060f2ed6f225161..666667555eab860f641002989f9ae3114801f8d1 100644
--- a/src/lua/init.cc
+++ b/src/lua/init.cc
@@ -56,7 +56,6 @@ extern "C" {
 #include "third_party/lua-cjson/lua_cjson.h"
 #include "third_party/lua-yaml/lyaml.h"
 #include "lua/msgpack.h"
-#include "lua/net_box.h"
 #include "lua/pickle.h"
 #include "lua/fio.h"
 
@@ -86,7 +85,6 @@ extern char strict_lua[],
 	uri_lua[],
 	bsdsocket_lua[],
 	console_lua[],
-	net_box_lua[],
 	help_lua[],
 	help_en_US_lua[],
 	tap_lua[],
@@ -120,7 +118,6 @@ static const char *lua_modules[] = {
 	"fio", fio_lua,
 	"csv", csv_lua,
 	"socket", bsdsocket_lua,
-	"net.box", net_box_lua,
 	"console", console_lua,
 	"tap", tap_lua,
 	"help.en_US", help_en_US_lua,
@@ -371,8 +368,6 @@ tarantool_lua_init(const char *tarantool_bin, int argc, char **argv)
 	lua_pop(L, 1);
 	luaopen_msgpack(L);
 	lua_pop(L, 1);
-	luaopen_net_box(L);
-	lua_pop(L, 1);
 
 #if defined(HAVE_GNU_READLINE)
 	/*
diff --git a/src/lua/utils.cc b/src/lua/utils.cc
index 51d0f603d7433f5df30cbbb26d4804c5ec58165e..8c23809dc7e4184b17ac440e30a04569d289d809 100644
--- a/src/lua/utils.cc
+++ b/src/lua/utils.cc
@@ -669,7 +669,7 @@ luaL_register_module(struct lua_State *L, const char *modname,
 	luaL_register(L, NULL, methods);
 }
 
-int
+void
 luaL_pushuint64(struct lua_State *L, uint64_t val)
 {
 	if (val < (1ULL << 52)) {
@@ -680,10 +680,9 @@ luaL_pushuint64(struct lua_State *L, uint64_t val)
 		*(uint64_t *) luaL_pushcdata(L, CTID_UINT64,
 					     sizeof(uint64_t)) = val;
 	}
-	return 1;
 }
 
-int
+void
 luaL_pushint64(struct lua_State *L, int64_t val)
 {
 	if (val > (-1LL << 52) && val < (1LL << 52)) {
@@ -694,7 +693,6 @@ luaL_pushint64(struct lua_State *L, int64_t val)
 		*(int64_t *) luaL_pushcdata(L, CTID_INT64,
 					    sizeof(int64_t)) = val;
 	}
-	return 1;
 }
 
 static inline int
diff --git a/src/lua/utils.h b/src/lua/utils.h
index 2cfce28b5f7da95ba88878372996cfcccfc57f48..a8420c02da62d308b8e0dd9a6f66c9ee6f10dd24 100644
--- a/src/lua/utils.h
+++ b/src/lua/utils.h
@@ -106,7 +106,7 @@ luaL_ctypeid(struct lua_State *L, const char *ctypename);
 /**
 * @brief Declare symbols for FFI
 * @param L Lua State
-* @param what C definitions
+* @param ctypename C definitions, e.g "struct stat"
 * @sa ffi.cdef(def)
 * @retval 0 on success
 * @retval LUA_ERRRUN, LUA_ERRMEM, LUA_ERRERR otherwise
@@ -372,19 +372,21 @@ luaL_register_module(struct lua_State *L, const char *modname,
 /** \cond public */
 
 /**
- * push uint64_t to Lua stack
+ * Push uint64_t onto the stack
  *
  * @param L is a Lua State
  * @param val is a value to push
- *
  */
-LUA_API int
+LUA_API void
 luaL_pushuint64(struct lua_State *L, uint64_t val);
 
 /**
- * @copydoc luaL_pushnumber64
+ * Push int64_t onto the stack
+ *
+ * @param L is a Lua State
+ * @param val is a value to push
  */
-LUA_API int
+LUA_API void
 luaL_pushint64(struct lua_State *L, int64_t val);
 
 /**
diff --git a/src/main.cc b/src/main.cc
index 7e5d98b6b19fc2099cee405cdee39663ece8d7c9..1a93791dd413f46a023ff96d7339acaa84ce9b03 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -53,7 +53,7 @@
 #include <crc32.h>
 #include "memory.h"
 #include <say.h>
-#include <stat.h>
+#include <rmean.h>
 #include <limits.h>
 #include "trivia/util.h"
 #include "tt_pthread.h"
diff --git a/src/memory.cc b/src/memory.cc
index 81265a28c9c47b1dcbb78276c6aab37eeb01b31d..1c4e55093a1c6922fbb7bc2b02d9e2f3031f6aae 100644
--- a/src/memory.cc
+++ b/src/memory.cc
@@ -37,7 +37,7 @@ void
 memory_init()
 {
 	static struct quota runtime_quota;
-	static const size_t SLAB_SIZE = 4 * 1024 * 1024;
+	const size_t SLAB_SIZE = 4 * 1024 * 1024;
 	/* default quota initialization */
 	quota_init(&runtime_quota, QUOTA_MAX);
 
diff --git a/src/rmean.cc b/src/rmean.cc
new file mode 100644
index 0000000000000000000000000000000000000000..0720bf01749f66c56cf92a1b00ce969d04a153bc
--- /dev/null
+++ b/src/rmean.cc
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2010-2015, 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 "rmean.h"
+
+#include <say.h>
+
+#include <assoc.h>
+
+
+void
+rmean_collect(struct rmean *rmean, size_t name, int64_t value)
+{
+	assert(name < rmean->stats_n);
+
+	rmean->stats[name].value[0] += value;
+	rmean->stats[name].total += value;
+}
+
+int
+rmean_foreach(struct rmean *rmean, rmean_cb cb, void *cb_ctx)
+{
+	for (size_t i = 0; i < rmean->stats_n; i++) {
+		if (rmean->stats[i].name == NULL)
+			continue;
+
+		int diff = 0;
+		for (size_t j = 1; j <= PERF_SECS; j++)
+			diff += rmean->stats[i].value[j];
+		/* value[0] not adds because second isn't over */
+
+		diff /= PERF_SECS;
+
+		int res = cb(rmean->stats[i].name, diff,
+			     rmean->stats[i].total, cb_ctx);
+		if (res != 0)
+			return res;
+	}
+	return 0;
+
+}
+
+void
+rmean_age(ev_loop * /* loop */,
+	 ev_timer *timer, int /* events */)
+{
+	struct rmean *rmean = (struct rmean *) timer->data;
+
+	for (size_t i = 0; i < rmean->stats_n; i++) {
+		if (rmean->stats[i].name == NULL)
+			continue;
+
+		for (int j = PERF_SECS - 1; j >= 0;  j--)
+			rmean->stats[i].value[j + 1] =
+					rmean->stats[i].value[j];
+		rmean->stats[i].value[0] = 0;
+	}
+
+	ev_timer_again(loop(), timer);
+}
+
+void
+rmean_timer_tick(struct rmean *rmean)
+{
+	rmean_age(loop(), &rmean->timer, 0);
+}
+
+struct rmean *
+rmean_new(const char **name, size_t n)
+{
+	struct rmean *rmean = (struct rmean *) realloc(NULL,
+				sizeof(struct rmean) +
+				sizeof(struct stats) * n);
+	if (rmean == NULL)
+		return NULL;
+	memset(rmean, 0, sizeof(struct rmean) + sizeof(struct stats) * n);
+	rmean->stats_n = n;
+	rmean->timer.data = (void *)rmean;
+	ev_timer_init(&rmean->timer, rmean_age, 0, 1.);
+	ev_timer_again(loop(), &rmean->timer);
+	for (size_t i = 0; i < n; i++, name++) {
+		rmean->stats[i].name = *name;
+
+		if (*name == NULL)
+			continue;
+	}
+	return rmean;
+}
+
+void
+rmean_delete(struct rmean *rmean)
+{
+
+	ev_timer_stop(loop(), &rmean->timer);
+	free(rmean);
+	rmean = 0;
+}
+
+void
+rmean_cleanup(struct rmean *rmean)
+{
+	for (size_t i = 0; i < rmean->stats_n; i++) {
+		for (size_t j = 0; j < PERF_SECS + 1; j++)
+			rmean->stats[i].value[j] = 0;
+		rmean->stats[i].total = 0;
+	}
+}
diff --git a/src/stat.h b/src/rmean.h
similarity index 66%
rename from src/stat.h
rename to src/rmean.h
index 0fc4aa7f15b76c5e94609b8f51e3175351d2421a..b6dd5848a933aa40a14abf8775a19ffcd094a037 100644
--- a/src/stat.h
+++ b/src/rmean.h
@@ -1,5 +1,5 @@
-#ifndef TARANTOOL_STAT_H_INCLUDED
-#define TARANTOOL_STAT_H_INCLUDED
+#ifndef TARANTOOL_RMEAN_H_INCLUDED
+#define TARANTOOL_RMEAN_H_INCLUDED
 /*
  * Copyright 2010-2015, Tarantool AUTHORS, please see AUTHORS file.
  *
@@ -34,16 +34,34 @@
 #include <stddef.h>
 #include <stdint.h>
 
-void stat_init(void);
-void stat_free(void);
-void stat_cleanup(int base, size_t max_idx);
-int stat_register(const char **name, size_t count);
-extern int stat_max_name_len;
+#include "trivia/util.h"
+#include "fiber.h"
 
-void stat_collect(int base, int name, int64_t value);
+#define PERF_SECS 5
 
-typedef int (*stat_cb)(const char *name, int rps, int64_t total, void *cb_ctx);
 
-int stat_foreach(stat_cb cb, void *cb_ctx);
+struct stats {
+	const char *name;
+	int64_t value[PERF_SECS + 1];
+	int64_t total;
+};
 
-#endif /* TARANTOOL_STAT_H_INCLUDED */
+struct rmean {
+	ev_timer timer;
+	int stats_n;
+	struct stats stats[0];
+};
+
+struct rmean *rmean_new(const char **name, size_t n);
+void rmean_delete(struct rmean *rmean);
+void rmean_cleanup(struct rmean *rmean);
+
+void rmean_timer_tick(struct rmean *rmean);
+
+void rmean_collect(struct rmean *rmean, size_t name, int64_t value);
+
+typedef int (*rmean_cb)(const char *name, int rps, int64_t total, void *cb_ctx);
+
+int rmean_foreach(struct rmean *rmean, rmean_cb cb, void *cb_ctx);
+
+#endif /* TARANTOOL_RMEAN_H_INCLUDED */
diff --git a/src/say.h b/src/say.h
index d3241d4ac5945cd2a38ca0d7daa62fbfc7984e1f..bb32329b5fff985e19fb8400a95c636858d6a86a 100644
--- a/src/say.h
+++ b/src/say.h
@@ -41,6 +41,7 @@ extern "C" {
 
 /** \cond public */
 
+/** Log levels */
 enum say_level {
 	S_FATAL,		/* do not this value use directly */
 	S_SYSERROR,
@@ -78,20 +79,50 @@ void vsay(int level, const char *filename, int line, const char *error,
 typedef void (*sayfunc_t)(int level, const char *filename, int line, const char *error,
                           const char *format, ...);
 
+/** Internal function used to implement say() macros */
 extern sayfunc_t _say __attribute__ ((format(printf, 5, 6)));
 
-#define say(level, ...) ({ _say(level, __FILE__, __LINE__, __VA_ARGS__); })
+/**
+ * Format and print a message to Tarantool log file.
+ *
+ * \param level (int) - log level (see enum \link say_level \endlink)
+ * \param format (const char * ) - printf()-like format string
+ * \param ... - format arguments
+ * \sa printf()
+ * \sa enum say_level
+ */
+#define say(level, format, ...) ({ _say(level, __FILE__, __LINE__, format, \
+	##__VA_ARGS__); })
+
+/**
+ * Format and print a message to Tarantool log file.
+ *
+ * \param format (const char * ) - printf()-like format string
+ * \param ... - format arguments
+ * \sa printf()
+ * \sa enum say_level
+ * Example:
+ * \code
+ * say_info("Some useful information: %s", status);
+ * \endcode
+ */
+#define say_error(format, ...) say(S_ERROR, NULL, format, ##__VA_ARGS__)
+/** \copydoc say_error() */
+#define say_crit(format, ...) say(S_CRIT, NULL, format, ##__VA_ARGS__)
+/** \copydoc say_error() */
+#define say_warn(format, ...) say(S_WARN, NULL, format, ##__VA_ARGS__)
+/** \copydoc say_error() */
+#define say_info(format, ...) say(S_INFO, NULL, format, ##__VA_ARGS__)
+/** \copydoc say_error() */
+#define say_debug(format, ...) say(S_DEBUG, NULL, format, ##__VA_ARGS__)
+/** \copydoc say_error(). */
+#define say_syserror(format, ...) say(S_SYSERROR, strerror(errno), format, \
+	##__VA_ARGS__)
+/** \endcond public */
 
 #define panic_status(status, ...)	({ say(S_FATAL, NULL, __VA_ARGS__); exit(status); })
 #define panic(...)			panic_status(EXIT_FAILURE, __VA_ARGS__)
 #define panic_syserror(...)		({ say(S_FATAL, strerror(errno), __VA_ARGS__); exit(EXIT_FAILURE); })
-#define say_syserror(...)		say(S_SYSERROR, strerror(errno), __VA_ARGS__)
-#define say_error(...)			say(S_ERROR, NULL, __VA_ARGS__)
-#define say_crit(...)			say(S_CRIT, NULL, __VA_ARGS__)
-#define say_warn(...)			say(S_WARN, NULL, __VA_ARGS__)
-#define say_info(...)			say(S_INFO, NULL, __VA_ARGS__)
-#define say_debug(...)			say(S_DEBUG, NULL, __VA_ARGS__)
-/** \endcond public */
 
 #if defined(__cplusplus)
 } /* extern "C" */
diff --git a/src/stat.cc b/src/stat.cc
deleted file mode 100644
index 5e9ed99548d68e48aae9d66680b220be4bad34b5..0000000000000000000000000000000000000000
--- a/src/stat.cc
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright 2010-2015, 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 "stat.h"
-
-#include "trivia/util.h"
-#include "fiber.h"
-#include <say.h>
-
-#include <assoc.h>
-
-#define SECS 5
-static ev_timer timer;
-
-struct stats {
-	const char *name;
-	int64_t value[SECS + 1];
-} *stats = NULL;
-static int stats_size = 0;
-static int stats_max = 0;
-static int base = 0;
-int stat_max_name_len = 0;
-
-static void
-stat_recalc_max_name_len()
-{
-	stat_max_name_len = 0;
-	for (unsigned i = 0; i <= stats_max; i++) {
-		if (stats[i].name != NULL)
-			stat_max_name_len = MAX(stat_max_name_len,
-						strlen(stats[i].name));
-	}
-}
-
-int
-stat_register(const char **name, size_t max_idx)
-{
-	int initial_base = base;
-
-	for (int i = 0; i < max_idx; i++, name++, base++) {
-		if (stats_size <= base) {
-			stats_size += 1024;
-			stats = (struct stats *) realloc(stats, sizeof(*stats) * stats_size);
-			if (stats == NULL)
-				abort();
-		}
-
-		stats[base].name = *name;
-
-		if (*name == NULL)
-			continue;
-
-		for (int i = 0; i < SECS + 1; i++)
-			stats[base].value[i] = 0;
-
-		stats_max = base;
-	}
-	stat_recalc_max_name_len();
-
-	return initial_base;
-}
-
-void
-stat_collect(int base, int name, int64_t value)
-{
-	stats[base + name].value[0] += value;
-	stats[base + name].value[SECS] += value;
-}
-
-int
-stat_foreach(stat_cb cb, void *cb_ctx)
-{
-	for (unsigned i = 0; i <= stats_max; i++) {
-		if (stats[i].name == NULL)
-			continue;
-
-		int diff = 0;
-		for (int j = 0; j < SECS; j++)
-			diff += stats[i].value[j];
-
-		diff /= SECS;
-
-		int res = cb(stats[i].name, diff,
-			     stats[i].value[SECS], cb_ctx);
-		if (res != 0)
-			return res;
-	}
-	return 0;
-
-}
-
-void
-stat_age(ev_loop * /* loop */, ev_timer *timer, int /* events */)
-{
-	if (stats == NULL)
-		return;
-
-	for (int i = 0; i <= stats_max; i++) {
-		if (stats[i].name == NULL)
-			continue;
-
-		for (int j = SECS - 2; j >= 0;  j--)
-			stats[i].value[j + 1] = stats[i].value[j];
-		stats[i].value[0] = 0;
-	}
-
-	ev_timer_again(loop(), timer);
-}
-
-void
-stat_init(void)
-{
-	ev_init(&timer, stat_age);
-	timer.repeat = 1.;
-	ev_timer_again(loop(), &timer);
-}
-
-void
-stat_free(void)
-{
-	ev_timer_stop(loop(), &timer);
-	if (stats)
-		free(stats);
-}
-
-void
-stat_cleanup(int base, size_t max_idx)
-{
-	for (int i = base; i < max_idx; i++)
-		for (int j = 0; j < SECS + 1; j++)
-			stats[i].value[j] = 0;
-}
diff --git a/src/trivia/config.h.cmake b/src/trivia/config.h.cmake
index fd3f035ee910bfcb8a75edd9478f7ac207ce4e6d..7f18dd749a3c03aeae4738e6ce0773b9c9c3ea5b 100644
--- a/src/trivia/config.h.cmake
+++ b/src/trivia/config.h.cmake
@@ -5,14 +5,24 @@
  * config.h.cmake. Please do not modify.
  */
 /** \cond public */
-/*
- * A string with major-minor-patch-commit-id identifier of the
- * release.
+
+/**
+ * Package major version - 1 for 1.6.7
  */
-#define PACKAGE_VERSION "@PACKAGE_VERSION@"
 #define PACKAGE_VERSION_MAJOR @CPACK_PACKAGE_VERSION_MAJOR@
+/**
+ * Package minor version - 6 for 1.6.7
+ */
 #define PACKAGE_VERSION_MINOR @CPACK_PACKAGE_VERSION_MINOR@
+/**
+ * Package patch version - 7 for 1.6.7
+ */
 #define PACKAGE_VERSION_PATCH @CPACK_PACKAGE_VERSION_PATCH@
+/**
+ * A string with major-minor-patch-commit-id identifier of the
+ * release, e.g. 1.6.6-113-g8399d0e.
+ */
+#define PACKAGE_VERSION "@PACKAGE_VERSION@"
 
 /** \endcond public */
 
@@ -161,25 +171,32 @@
 
 /** \cond public */
 
-/*
- * predefined /etc directory prefix.
- */
+/** System configuration dir (e.g /etc) */
 #define SYSCONF_DIR "@CMAKE_INSTALL_SYSCONFDIR@"
+/** Install prefix (e.g. /usr) */
 #define INSTALL_PREFIX "@CMAKE_INSTALL_PREFIX@"
+/** Build type, e.g. Debug or Release */
 #define BUILD_TYPE "@CMAKE_BUILD_TYPE@"
+/** CMake build type signature, e.g. Linux-x86_64-Debug */
 #define BUILD_INFO "@TARANTOOL_BUILD@"
+/** Command line used to run CMake */
 #define BUILD_OPTIONS "cmake . @TARANTOOL_OPTIONS@"
+/** Pathes to C and CXX compilers */
 #define COMPILER_INFO "@CMAKE_C_COMPILER@ @CMAKE_CXX_COMPILER@"
+/** C compile flags used to build Tarantool */
 #define TARANTOOL_C_FLAGS "@TARANTOOL_C_FLAGS@"
+/** CXX compile flags used to build Tarantool */
 #define TARANTOOL_CXX_FLAGS "@TARANTOOL_CXX_FLAGS@"
 
-/*
- * Modules
- */
+/** A path to install *.lua module files */
 #define MODULE_LIBDIR "@MODULE_FULL_LIBDIR@"
+/** A path to install *.so / *.dylib module files */
 #define MODULE_LUADIR "@MODULE_FULL_LUADIR@"
+/** A path to Lua includes (the same directory where this file is contained) */
 #define MODULE_INCLUDEDIR "@MODULE_FULL_INCLUDEDIR@"
+/** A constant added to package.path in Lua to find *.lua module files */
 #define MODULE_LUAPATH "@MODULE_LUAPATH@"
+/** A constant added to package.cpath in Lua to find *.so module files */
 #define MODULE_LIBPATH "@MODULE_LIBPATH@"
 
 /** \endcond public */
diff --git a/src/trivia/tarantool_header.h b/src/trivia/tarantool_header.h
index c6944e5820347a6f01f352c82df6e5eb0062dcba..2223de9a6c115c0fdef98d01a73fcd0d14dbacce 100644
--- a/src/trivia/tarantool_header.h
+++ b/src/trivia/tarantool_header.h
@@ -14,6 +14,7 @@
  * Tarantool Module API
  */
 
+/** Extern modificator for all public functions */
 #if defined(__cplusplus)
 #define API_EXPORT extern "C" __attribute__ ((visibility ("default")))
 #else
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 4cf0ba46b990146170c9132c1c7185e857a2b6ac..d1561644a8b321253ca40a3c821da0b034a71984 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -4,7 +4,7 @@ include_directories(${CMAKE_BINARY_DIR}/src/trivia)
 function(build_module module files)
     add_library(${module} SHARED ${files})
     set_target_properties(${module} PROPERTIES PREFIX "")
-    add_dependencies(${module} rebuild_module_api)
+    add_dependencies(${module} api)
     if(TARGET_OS_DARWIN)
         set_target_properties(${module} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
     endif(TARGET_OS_DARWIN)
diff --git a/test/box/access_sysview.result b/test/box/access_sysview.result
index c83e77337623689e9751544d5793e2775cb404c0..39e200c4e22e16b639acbe08df2efe3572fa332a 100644
--- a/test/box/access_sysview.result
+++ b/test/box/access_sysview.result
@@ -27,7 +27,7 @@ session = box.session
 -- error: sysview does not support replace()
 box.space._vspace:replace({1, 1, 'test'})
 ---
-- error: sysview does not support replace()
+- error: sysview does not support replace
 ...
 session.su('guest')
 ---
diff --git a/test/box/admin.result b/test/box/admin.result
index 7a2e8cb1a9bb7a328582a40ac240b7a1305da304..77b693a30995027f7f6ac6204cd19d57907ee4f3 100644
--- a/test/box/admin.result
+++ b/test/box/admin.result
@@ -27,6 +27,7 @@ cfg_filter(box.cfg)
   sophia:
     page_size: 131072
     memory_limit: 0
+    compression_key: 0
     threads: 5
     node_size: 134217728
     compression: none
diff --git a/test/box/cfg.result b/test/box/cfg.result
index 5269212912bf4b0cc28db18fef755ee6c0053382..99f1955a8e1d87b33578ef288c4c9d445c4ea3e7 100644
--- a/test/box/cfg.result
+++ b/test/box/cfg.result
@@ -2,7 +2,7 @@
 --# push filter 'admin: .*' to 'admin: <uri>'
 box.cfg.nosuchoption = 1
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:271: Attempt to modify a read-only
+- error: '[string "-- load_cfg.lua - internal file..."]:273: Attempt to modify a read-only
     table'
 ...
 cfg_filter(box.cfg)
@@ -16,6 +16,7 @@ cfg_filter(box.cfg)
   sophia:
     page_size: 131072
     memory_limit: 0
+    compression_key: 0
     threads: 5
     node_size: 134217728
     compression: none
@@ -34,7 +35,7 @@ cfg_filter(box.cfg)
 -- must be read-only
 box.cfg()
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:212: bad argument #1 to ''pairs''
+- error: '[string "-- load_cfg.lua - internal file..."]:214: bad argument #1 to ''pairs''
     (table expected, got nil)'
 ...
 cfg_filter(box.cfg)
@@ -48,6 +49,7 @@ cfg_filter(box.cfg)
   sophia:
     page_size: 131072
     memory_limit: 0
+    compression_key: 0
     threads: 5
     node_size: 134217728
     compression: none
@@ -66,23 +68,23 @@ cfg_filter(box.cfg)
 -- check that cfg with unexpected parameter fails.
 box.cfg{sherlock = 'holmes'}
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:168: Error: cfg parameter
+- error: '[string "-- load_cfg.lua - internal file..."]:170: Error: cfg parameter
     ''sherlock'' is unexpected'
 ...
 -- check that cfg with unexpected type of parameter failes
 box.cfg{listen = {}}
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:188: Error: cfg parameter
+- error: '[string "-- load_cfg.lua - internal file..."]:190: Error: cfg parameter
     ''listen'' should be one of types: string, number'
 ...
 box.cfg{wal_dir = 0}
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:182: Error: cfg parameter
+- error: '[string "-- load_cfg.lua - internal file..."]:184: Error: cfg parameter
     ''wal_dir'' should be of type string'
 ...
 box.cfg{coredump = 'true'}
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:182: Error: cfg parameter
+- error: '[string "-- load_cfg.lua - internal file..."]:184: Error: cfg parameter
     ''coredump'' should be of type boolean'
 ...
 --------------------------------------------------------------------------------
@@ -90,17 +92,17 @@ box.cfg{coredump = 'true'}
 --------------------------------------------------------------------------------
 box.cfg{slab_alloc_arena = "100500"}
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:182: Error: cfg parameter
+- error: '[string "-- load_cfg.lua - internal file..."]:184: Error: cfg parameter
     ''slab_alloc_arena'' should be of type number'
 ...
 box.cfg{sophia = "sophia"}
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:176: Error: cfg parameter
+- error: '[string "-- load_cfg.lua - internal file..."]:178: Error: cfg parameter
     ''sophia'' should be a table'
 ...
 box.cfg{sophia = {threads = "threads"}}
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:182: Error: cfg parameter
+- error: '[string "-- load_cfg.lua - internal file..."]:184: Error: cfg parameter
     ''sophia.threads'' should be of type number'
 ...
 --------------------------------------------------------------------------------
diff --git a/test/box/function1.c b/test/box/function1.c
index 3af9773c199464e665d77077413fbf7bdc87be3e..85b83da63008331722f1c0770203792fb6acc9e7 100644
--- a/test/box/function1.c
+++ b/test/box/function1.c
@@ -115,6 +115,7 @@ errors(box_function_ctx_t *ctx, const char *args, const char *args_end)
 	assert(strcmp(box_error_type(error), "ClientError") == 0);
 	assert(box_error_code(error) == ER_PROC_C);
 	assert(strcmp(box_error_message(error), "Proc error") == 0);
+	(void) error;
 
 	box_error_clear();
 	assert(box_error_last() == NULL);
diff --git a/test/box/misc.result b/test/box/misc.result
index c7b5e72976bcd0105f7f920617e6feb4eb7951bc..03daae50f2ebdc57bdd98d77c9b7104cbb3c87d3 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -60,6 +60,7 @@ t
   - session
   - slab
   - snapshot
+  - sophia
   - space
   - stat
   - tuple
@@ -145,6 +146,7 @@ t;
   - REPLACE
   - UPSERT
   - AUTH
+  - ERROR
   - UPDATE
   - total
   - rps
@@ -248,6 +250,7 @@ t;
   - 'box.error.CREATE_FUNCTION : 50'
   - 'box.error.SOPHIA : 60'
   - 'box.error.NO_SUCH_INDEX : 35'
+  - 'box.error.UNKNOWN_RTREE_INDEX_DISTANCE_TYPE : 103'
   - 'box.error.TUPLE_IS_TOO_LONG : 27'
   - 'box.error.UNKNOWN_SERVER : 62'
   - 'box.error.FUNCTION_EXISTS : 52'
diff --git a/test/box/net.box.result b/test/box/net.box.result
index 2f38086ed423cfa437c3a54dfed8a87a4f2c668a..7f0da9fe206261d2c832c9d4f401df1dc68993de 100644
--- a/test/box/net.box.result
+++ b/test/box/net.box.result
@@ -288,7 +288,8 @@ cn.space.net_box_test_space:insert{234, 1,2,3}
 ...
 cn.space.net_box_test_space.insert{234, 1,2,3}
 ---
-- error: 'builtin/net.box.lua:130: Use space:method(...) instead space.method(...)'
+- error: '[string "-- net_box.lua (internal file)..."]:133: Use space:method(...)
+    instead space.method(...)'
 ...
 cn.space.net_box_test_space:replace{354, 1,2,3}
 ---
@@ -905,6 +906,49 @@ r = c.space.test:select(nil, {limit=5000})
 box.space.test:drop()
 ---
 ...
+-- gh-970 gh-971 UPSERT over network
+_ = box.schema.space.create('test')
+---
+...
+_ = box.space.test:create_index('primary', {type = 'TREE', parts = {1,'NUM'}})
+---
+...
+_ = box.space.test:insert{1, 2, "string"}
+---
+...
+c = net:new(box.cfg.listen)
+---
+...
+c.space.test:select{}
+---
+- - [1, 2, 'string']
+...
+c.space.test:upsert({1}, {{'+', 2, 1}}, {10, 20, 'nothing'}) -- common update
+---
+...
+c.space.test:select{}
+---
+- - [1, 3, 'string']
+...
+c.space.test:upsert({2}, {{'+', 2, 1}}, {2, 4, 'something'}) -- insert
+---
+...
+c.space.test:select{}
+---
+- - [1, 3, 'string']
+  - [2, 4, 'something']
+...
+c.space.test:upsert({2}, {{'+', 3, 100500}}, {2, 4, 'nothing'}) -- wrong operation
+---
+...
+c.space.test:select{}
+---
+- - [1, 3, 'string']
+  - [2, 4, 'something']
+...
+box.space.test:drop()
+---
+...
 box.schema.user.revoke('guest', 'read,write,execute', 'universe')
 ---
 ...
diff --git a/test/box/net.box.test.lua b/test/box/net.box.test.lua
index 0ea5f5b40e89a2dfc06d39d4fad8133fa47f7a9c..5942779adbd246d12bcaf679b7e9050bfbc8e92b 100644
--- a/test/box/net.box.test.lua
+++ b/test/box/net.box.test.lua
@@ -379,4 +379,18 @@ c = net:new(box.cfg.listen)
 r = c.space.test:select(nil, {limit=5000})
 box.space.test:drop()
 
+-- gh-970 gh-971 UPSERT over network
+_ = box.schema.space.create('test')
+_ = box.space.test:create_index('primary', {type = 'TREE', parts = {1,'NUM'}})
+_ = box.space.test:insert{1, 2, "string"}
+c = net:new(box.cfg.listen)
+c.space.test:select{}
+c.space.test:upsert({1}, {{'+', 2, 1}}, {10, 20, 'nothing'}) -- common update
+c.space.test:select{}
+c.space.test:upsert({2}, {{'+', 2, 1}}, {2, 4, 'something'}) -- insert
+c.space.test:select{}
+c.space.test:upsert({2}, {{'+', 3, 100500}}, {2, 4, 'nothing'}) -- wrong operation
+c.space.test:select{}
+box.space.test:drop()
+
 box.schema.user.revoke('guest', 'read,write,execute', 'universe')
diff --git a/test/box/rtree_misc.result b/test/box/rtree_misc.result
index 9b5183d5fd4bbe726ed64d9a48c79aefcc88f114..fa15112896e2c505e8ddc1f1b381180131304885 100644
--- a/test/box/rtree_misc.result
+++ b/test/box/rtree_misc.result
@@ -262,11 +262,6 @@ box.snapshot()
 ---
 - ok
 ...
-for i = 11,20 do s:insert{i, i, i + 1, i + 1}
----
-- error: '[string "for i = 11,20 do s:insert{i, i, i + 1, i + 1} "]:1: ''end'' expected
-    near ''<eof>'''
-...
 i:select({0, 0}, {iterator = 'neighbor'})
 ---
 - - [1, 1, [1, 1, 2, 2]]
@@ -304,3 +299,185 @@ i:select({0, 0}, {iterator = 'neighbor'})
 s:drop()
 ---
 ...
+s = box.schema.space.create('spatial')
+---
+...
+i = s:create_index('primary', { type = 'tree', parts = {1, 'num'}})
+---
+...
+i = s:create_index('spatial', { type = 'rtree', unique = false, parts = {3, 'array'}, dimension = 4})
+---
+...
+for i = 1,10 do s:insert{i, i, {i, i, i, i, i + 1, i + 1, i + 1, i + 1}} end
+---
+...
+box.snapshot()
+---
+- ok
+...
+i:select({0, 0, 0, 0}, {iterator = 'neighbor'})
+---
+- - [1, 1, [1, 1, 1, 1, 2, 2, 2, 2]]
+  - [2, 2, [2, 2, 2, 2, 3, 3, 3, 3]]
+  - [3, 3, [3, 3, 3, 3, 4, 4, 4, 4]]
+  - [4, 4, [4, 4, 4, 4, 5, 5, 5, 5]]
+  - [5, 5, [5, 5, 5, 5, 6, 6, 6, 6]]
+  - [6, 6, [6, 6, 6, 6, 7, 7, 7, 7]]
+  - [7, 7, [7, 7, 7, 7, 8, 8, 8, 8]]
+  - [8, 8, [8, 8, 8, 8, 9, 9, 9, 9]]
+  - [9, 9, [9, 9, 9, 9, 10, 10, 10, 10]]
+  - [10, 10, [10, 10, 10, 10, 11, 11, 11, 11]]
+...
+--# stop server default
+--# start server default
+s = box.space.spatial
+---
+...
+i = s.index.spatial
+---
+...
+i:select({0, 0, 0, 0}, {iterator = 'neighbor'})
+---
+- - [1, 1, [1, 1, 1, 1, 2, 2, 2, 2]]
+  - [2, 2, [2, 2, 2, 2, 3, 3, 3, 3]]
+  - [3, 3, [3, 3, 3, 3, 4, 4, 4, 4]]
+  - [4, 4, [4, 4, 4, 4, 5, 5, 5, 5]]
+  - [5, 5, [5, 5, 5, 5, 6, 6, 6, 6]]
+  - [6, 6, [6, 6, 6, 6, 7, 7, 7, 7]]
+  - [7, 7, [7, 7, 7, 7, 8, 8, 8, 8]]
+  - [8, 8, [8, 8, 8, 8, 9, 9, 9, 9]]
+  - [9, 9, [9, 9, 9, 9, 10, 10, 10, 10]]
+  - [10, 10, [10, 10, 10, 10, 11, 11, 11, 11]]
+...
+s:drop()
+---
+...
+-- distance type
+iopts = { type = 'rtree', unique = false, parts = {2, 'array'} }
+---
+...
+iopts['distance'] = 'euclid'
+---
+...
+s = box.schema.space.create('spatial')
+---
+...
+i = s:create_index('primary', { type = 'tree', parts = {1, 'num'}})
+---
+...
+i = s:create_index('spatial', iopts)
+---
+...
+s:insert{1, {0, 5}}
+---
+- [1, [0, 5]]
+...
+s:insert{2, {5, 0}}
+---
+- [2, [5, 0]]
+...
+s:insert{3, {5, 5}}
+---
+- [3, [5, 5]]
+...
+s:insert{4, {8, 0}}
+---
+- [4, [8, 0]]
+...
+s:insert{5, {0, 8}}
+---
+- [5, [0, 8]]
+...
+s.index.spatial:select({{0, 0}}, {iterator = 'neighbor'})
+---
+- - [1, [0, 5]]
+  - [2, [5, 0]]
+  - [3, [5, 5]]
+  - [4, [8, 0]]
+  - [5, [0, 8]]
+...
+s:drop()
+---
+...
+iopts = { type = 'rtree', unique = false, parts = {2, 'array'} }
+---
+...
+iopts['distance'] = 'manhattan'
+---
+...
+s = box.schema.space.create('spatial')
+---
+...
+i = s:create_index('primary', { type = 'tree', parts = {1, 'num'}})
+---
+...
+i = s:create_index('spatial', iopts)
+---
+...
+s:insert{1, {0, 5}}
+---
+- [1, [0, 5]]
+...
+s:insert{2, {5, 0}}
+---
+- [2, [5, 0]]
+...
+s:insert{3, {5, 5}}
+---
+- [3, [5, 5]]
+...
+s:insert{4, {8, 0}}
+---
+- [4, [8, 0]]
+...
+s:insert{5, {0, 8}}
+---
+- [5, [0, 8]]
+...
+s.index.spatial:select({{0, 0}}, {iterator = 'neighbor'})
+---
+- - [1, [0, 5]]
+  - [2, [5, 0]]
+  - [4, [8, 0]]
+  - [5, [0, 8]]
+  - [3, [5, 5]]
+...
+--# stop server default
+--# start server default
+s = box.space.spatial
+---
+...
+i = s.index.spatial
+---
+...
+s.index.spatial:select({{0, 0}}, {iterator = 'neighbor'})
+---
+- - [1, [0, 5]]
+  - [2, [5, 0]]
+  - [4, [8, 0]]
+  - [5, [0, 8]]
+  - [3, [5, 5]]
+...
+box.snapshot()
+---
+- ok
+...
+--# stop server default
+--# start server default
+s = box.space.spatial
+---
+...
+i = s.index.spatial
+---
+...
+s.index.spatial:select({{0, 0}}, {iterator = 'neighbor'})
+---
+- - [1, [0, 5]]
+  - [2, [5, 0]]
+  - [4, [8, 0]]
+  - [5, [0, 8]]
+  - [3, [5, 5]]
+...
+s:drop()
+---
+...
diff --git a/test/box/rtree_misc.test.lua b/test/box/rtree_misc.test.lua
index fdc21ce307428d219d3683352d8e22a6144750d6..20cee17dc5c55548e1e896315deb85307c335c58 100644
--- a/test/box/rtree_misc.test.lua
+++ b/test/box/rtree_misc.test.lua
@@ -103,7 +103,6 @@ i = s:create_index('primary', { type = 'tree', parts = {1, 'num'}})
 i = s:create_index('spatial', { type = 'rtree', unique = false, parts = {3, 'array'}})
 for i = 1,10 do s:insert{i, i, {i, i, i + 1, i + 1}} end
 box.snapshot()
-for i = 11,20 do s:insert{i, i, i + 1, i + 1}
 i:select({0, 0}, {iterator = 'neighbor'})
 --# stop server default
 --# start server default
@@ -112,3 +111,53 @@ i = s.index.spatial
 i:select({0, 0}, {iterator = 'neighbor'})
 s:drop()
 
+s = box.schema.space.create('spatial')
+i = s:create_index('primary', { type = 'tree', parts = {1, 'num'}})
+i = s:create_index('spatial', { type = 'rtree', unique = false, parts = {3, 'array'}, dimension = 4})
+for i = 1,10 do s:insert{i, i, {i, i, i, i, i + 1, i + 1, i + 1, i + 1}} end
+box.snapshot()
+i:select({0, 0, 0, 0}, {iterator = 'neighbor'})
+--# stop server default
+--# start server default
+s = box.space.spatial
+i = s.index.spatial
+i:select({0, 0, 0, 0}, {iterator = 'neighbor'})
+s:drop()
+
+-- distance type
+iopts = { type = 'rtree', unique = false, parts = {2, 'array'} }
+iopts['distance'] = 'euclid'
+s = box.schema.space.create('spatial')
+i = s:create_index('primary', { type = 'tree', parts = {1, 'num'}})
+i = s:create_index('spatial', iopts)
+s:insert{1, {0, 5}}
+s:insert{2, {5, 0}}
+s:insert{3, {5, 5}}
+s:insert{4, {8, 0}}
+s:insert{5, {0, 8}}
+s.index.spatial:select({{0, 0}}, {iterator = 'neighbor'})
+s:drop()
+
+iopts = { type = 'rtree', unique = false, parts = {2, 'array'} }
+iopts['distance'] = 'manhattan'
+s = box.schema.space.create('spatial')
+i = s:create_index('primary', { type = 'tree', parts = {1, 'num'}})
+i = s:create_index('spatial', iopts)
+s:insert{1, {0, 5}}
+s:insert{2, {5, 0}}
+s:insert{3, {5, 5}}
+s:insert{4, {8, 0}}
+s:insert{5, {0, 8}}
+s.index.spatial:select({{0, 0}}, {iterator = 'neighbor'})
+--# stop server default
+--# start server default
+s = box.space.spatial
+i = s.index.spatial
+s.index.spatial:select({{0, 0}}, {iterator = 'neighbor'})
+box.snapshot()
+--# stop server default
+--# start server default
+s = box.space.spatial
+i = s.index.spatial
+s.index.spatial:select({{0, 0}}, {iterator = 'neighbor'})
+s:drop()
diff --git a/test/box/stat.result b/test/box/stat.result
index f497bce2950ac9ed7361cea9924209f0c141f798..4ea136fccd32e24696f54bc0557b7b12015f7c29 100644
--- a/test/box/stat.result
+++ b/test/box/stat.result
@@ -21,6 +21,10 @@ box.stat.SELECT.total
 ---
 - 0
 ...
+box.stat.ERROR.total
+---
+- 0
+...
 space = box.schema.space.create('tweedledum')
 ---
 ...
@@ -50,6 +54,15 @@ box.stat.REPLACE.total
 ...
 box.stat.SELECT.total
 ---
+- 2
+...
+-- check exceptions
+space:get('Impossible value')
+---
+- error: 'Supplied key type of part 0 does not match index part type: expected NUM'
+...
+box.stat.ERROR.total
+---
 - 1
 ...
 --# stop server default
@@ -75,6 +88,10 @@ box.stat.SELECT.total
 ---
 - 0
 ...
+box.stat.ERROR.total
+---
+- 0
+...
 -- cleanup
 box.space.tweedledum:drop()
 ---
diff --git a/test/box/stat.test.lua b/test/box/stat.test.lua
index 5ff384a4d19608c9320d71fc863e4dd644691b95..7484c781cfd41becb98fb988e7ff48d8e4f8cc24 100644
--- a/test/box/stat.test.lua
+++ b/test/box/stat.test.lua
@@ -6,6 +6,7 @@ box.stat.DELETE.total
 box.stat.UPDATE.total
 box.stat.REPLACE.total
 box.stat.SELECT.total
+box.stat.ERROR.total
 
 space = box.schema.space.create('tweedledum')
 index = space:create_index('primary', { type = 'hash' })
@@ -19,6 +20,10 @@ box.stat.UPDATE.total
 box.stat.REPLACE.total
 box.stat.SELECT.total
 
+-- check exceptions
+space:get('Impossible value')
+box.stat.ERROR.total
+
 --# stop server default
 --# start server default
 
@@ -28,6 +33,7 @@ box.stat.DELETE.total
 box.stat.UPDATE.total
 box.stat.REPLACE.total
 box.stat.SELECT.total
+box.stat.ERROR.total
 
 -- cleanup
 box.space.tweedledum:drop()
diff --git a/test/box/stat_net.result b/test/box/stat_net.result
new file mode 100644
index 0000000000000000000000000000000000000000..fa3ca518dc1e1bcc7e60df7182aead09184c664e
--- /dev/null
+++ b/test/box/stat_net.result
@@ -0,0 +1,57 @@
+-- clear statistics
+--# stop server default
+--# start server default
+box.stat.net.SENT -- zero
+---
+- total: 0
+  rps: 0
+...
+box.stat.net.RECEIVED -- zero
+---
+- total: 0
+  rps: 0
+...
+space = box.schema.space.create('tweedledum')
+---
+...
+box.schema.user.grant('guest','read,write,execute','universe')
+---
+...
+index = space:create_index('primary', { type = 'hash' })
+---
+...
+remote = require 'net.box'
+---
+...
+LISTEN = require('uri').parse(box.cfg.listen)
+---
+...
+cn = remote:new(LISTEN.host, LISTEN.service)
+---
+...
+cn.space.tweedledum:select() --small request
+---
+- []
+...
+box.stat.net.SENT.total > 0
+---
+- true
+...
+box.stat.net.RECEIVED.total > 0
+---
+- true
+...
+box.stat.net.EVENTS.total > 0
+---
+- true
+...
+box.stat.net.LOCKS.total > 0
+---
+- true
+...
+space:drop()
+---
+...
+cn:close()
+---
+...
diff --git a/test/box/stat_net.test.lua b/test/box/stat_net.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..62eaf09ecf3425de172711d5671be0ef5680f751
--- /dev/null
+++ b/test/box/stat_net.test.lua
@@ -0,0 +1,24 @@
+-- clear statistics
+--# stop server default
+--# start server default
+
+box.stat.net.SENT -- zero
+box.stat.net.RECEIVED -- zero
+
+space = box.schema.space.create('tweedledum')
+box.schema.user.grant('guest','read,write,execute','universe')
+index = space:create_index('primary', { type = 'hash' })
+remote = require 'net.box'
+
+LISTEN = require('uri').parse(box.cfg.listen)
+cn = remote:new(LISTEN.host, LISTEN.service)
+
+cn.space.tweedledum:select() --small request
+
+box.stat.net.SENT.total > 0
+box.stat.net.RECEIVED.total > 0
+box.stat.net.EVENTS.total > 0
+box.stat.net.LOCKS.total > 0
+
+space:drop()
+cn:close()
diff --git a/test/long_run/suite.lua b/test/long_run/suite.lua
index 91ed03e6f34dcd69d0abf2d28ab7f3edfe18d7e4..0b33dec7d766907ce938ca73f881be49ab77b97e 100644
--- a/test/long_run/suite.lua
+++ b/test/long_run/suite.lua
@@ -1,107 +1,111 @@
 
 function string_function()
-	local random_number
-	local random_string
-	random_string = ""
-	for x = 1,20,1 do
-		random_number = math.random(65, 90)
-		random_string = random_string .. string.char(random_number)
-	end
-	return random_string
+    local random_number
+    local random_string
+    random_string = ""
+    for x = 1,20,1 do
+        random_number = math.random(65, 90)
+        random_string = random_string .. string.char(random_number)
+    end
+    return random_string
 end
 
 function delete_replace_update(engine_name)
-	local string_value
-	if (box.space._space.index.name:select{'tester'}[1] ~= nil) then
-		box.space.tester:drop()
-	end
-	box.schema.space.create('tester', {engine=engine_name})
-	box.space.tester:create_index('primary',{type = 'tree', parts = {2, 'STR'}})
+    local string_value
+    if (box.space._space.index.name:select{'tester'}[1] ~= nil) then
+        box.space.tester:drop()
+    end
+    box.schema.space.create('tester', {engine=engine_name})
+    box.space.tester:create_index('primary',{type = 'tree', parts = {1, 'STR'}})
 
-	counter = 1
-	while counter < 100000 do
-		string_value = string_function()
+    local random_number
+    local string_value_2
+    local string_value_3
+    local counter = 1
+    while counter < 100000 do
+        local string_value = string_function()
 
-		string_table = box.space.tester.index.primary:select({string_value}, {iterator = 'GE', limit = 1})
-		if string_table[1] == nil then
-			box.space.tester:insert{counter, string_value}
-			string_value_2 = string_value
-		else
-			string_value_2 = string_table[1][2]
-		end
+        local string_table = box.space.tester.index.primary:select({string_value}, {iterator = 'GE', limit = 1})
+        if string_table[1] == nil then
+            box.space.tester:insert{string_value, counter}
+            string_value_2 = string_value
+        else
+            string_value_2 = string_table[1][1]
+        end
 
-		if string_value_2 == nil then
-			box.space.tester:insert{counter, string_value}
-			string_value_2 = string_value
-		end
+        if string_value_2 == nil then
+            box.space.tester:insert{string_value, counter}
+            string_value_2 = string_value
+        end
 
-		random_number = math.random(1,6)
+        random_number = math.random(1,6)
 
-		string_value_3 = string_function()
---		print('<'..counter..'> [' ..  random_number .. '] value_2: ' .. string_value_2 .. ' value_3: ' .. string_value_3)
-		if random_number == 1 then
-			box.space.tester:delete{string_value_2}
-		end
-		if random_number == 2 then
-			box.space.tester:replace{counter, string_value_2, string_value_3}
-		end
-		if random_number == 3 then
-			box.space.tester:delete{string_value_2}
-			box.space.tester:insert{counter, string_value_2}
-		end
-		if random_number == 4 then
-			if counter < 1000000 then
-				box.space.tester:delete{string_value_3}
-				box.space.tester:insert{counter, string_value_3, string_value_2}
-			end
-		end
-		if random_number == 5 then
-			box.space.tester:update({string_value_2}, {{'=', 1, string_value_3}})
-		end
-		if random_number == 6 then
-			box.space.tester:update({string_value_2}, {{'=', 1, string_value_3}})
-		end
-		counter = counter + 1
-	end
+        string_value_3 = string_function()
+--      print('<'..counter..'> [' ..  random_number .. '] value_2: ' .. string_value_2 .. ' value_3: ' .. string_value_3)
+        if random_number == 1 then
+            box.space.tester:delete{string_value_2}
+        end
+        if random_number == 2 then
+            box.space.tester:replace{string_value_2, counter, string_value_3}
+        end
+        if random_number == 3 then
+            box.space.tester:delete{string_value_2}
+            box.space.tester:insert{string_value_2, counter}
+        end
+        if random_number == 4 then
+            if counter < 1000000 then
+                box.space.tester:delete{string_value_3}
+                box.space.tester:insert{string_value_3, counter, string_value_2}
+            end
+        end
+        if random_number == 5 then
+            box.space.tester:update({string_value_2}, {{'=', 2, string_value_3}})
+        end
+        if random_number == 6 then
+            box.space.tester:update({string_value_2}, {{'=', 2, string_value_3}})
+        end
+        counter = counter + 1
+    end
 
-	box.space.tester:drop()
-	return {counter, random_number, string_value_2, string_value_3}
+    box.space.tester:drop()
+    return {counter, random_number, string_value_2, string_value_3}
 end
 
 function delete_insert(engine_name)
-	local string_value
-	if (box.space._space.index.name:select{'tester'}[1] ~= nil) then
-		box.space.tester:drop()
-	end
-	box.schema.space.create('tester', {engine=engine_name})
-	box.space.tester:create_index('primary',{type = 'tree', parts = {2, 'STR'}})
-	counter = 1
-	while counter < 100000 do
-		string_value = string_function()
-		string_table = box.space.tester.index.primary:select({string_value}, {iterator = 'GE', limit = 1})
+    local string_value
+    if (box.space._space.index.name:select{'tester'}[1] ~= nil) then
+        box.space.tester:drop()
+    end
+    box.schema.space.create('tester', {engine=engine_name})
+    box.space.tester:create_index('primary',{type = 'tree', parts = {1, 'STR'}})
+    local string_value_2
+    local counter = 1
+    while counter < 100000 do
+        local string_value = string_function()
+        local string_table = box.space.tester.index.primary:select({string_value}, {iterator = 'GE', limit = 1})
 
-		if string_table[1] == nil then
-			-- print (1, ' insert', counter, string_value)
-			box.space.tester:insert{counter, string_value}
-			string_value_2 = string_value
-		else
-			string_value_2 = string_table[1][2]
-		end
+        if string_table[1] == nil then
+            -- print (1, ' insert', counter, string_value)
+            box.space.tester:insert{string_value, counter}
+            string_value_2 = string_value
+        else
+            string_value_2 = string_table[1][1]
+        end
 
-		if string_value_2 == nil then
-			-- print (2, ' insert', counter, string_value)
-			box.space.tester:insert{counter, string_value}
-			string_value_2 = string_value
-		end
+        if string_value_2 == nil then
+            -- print (2, ' insert', counter, string_value)
+            box.space.tester:insert{string_value, counter}
+            string_value_2 = string_value
+        end
 
-		-- print (3, ' delete', counter, string_value_2)
-		box.space.tester:delete{string_value_2}
+        -- print (3, ' delete', counter, string_value_2)
+        box.space.tester:delete{string_value_2}
 
-		-- print (4, ' insert', counter, string_value_2)
-		box.space.tester:insert{counter, string_value_2}
+        -- print (4, ' insert', counter, string_value_2)
+        box.space.tester:insert{string_value_2, counter}
 
-		counter = counter + 1
-	end
-	box.space.tester:drop()
-	return {counter, string_value_2}
+        counter = counter + 1
+    end
+    box.space.tester:drop()
+    return {counter, string_value_2}
 end
diff --git a/test/replication/sophia_join.result b/test/replication/sophia_join.result
index ece1d73df3c21f82774c65f606769df2cabd4b6f..d3f227e2aa8dffa66143d677753bfba3093a0ed0 100644
--- a/test/replication/sophia_join.result
+++ b/test/replication/sophia_join.result
@@ -150,13 +150,3 @@ box.snapshot()
 ---
 - ok
 ...
-ffi = require('ffi')
----
-...
-ffi.cdef("int sophia_schedule(void);")
----
-...
-ffi.C.sophia_schedule() >= 0
----
-- true
-...
diff --git a/test/replication/sophia_join.test.py b/test/replication/sophia_join.test.py
index 1db923602fe99d633833c64b96f43762da4ebe83..9d0a1b3a76a766583b1d5de7b10434e21fa29794 100644
--- a/test/replication/sophia_join.test.py
+++ b/test/replication/sophia_join.test.py
@@ -32,6 +32,3 @@ replica.cleanup(True)
 # remove space
 master.admin("space:drop()")
 master.admin('box.snapshot()')
-master.admin("ffi = require('ffi')")
-master.admin("ffi.cdef(\"int sophia_schedule(void);\")")
-master.admin("ffi.C.sophia_schedule() >= 0")
diff --git a/test/sophia/conflict.lua b/test/sophia/conflict.lua
index d3cc350dac9788dfcc5bf9d6cdb40f349593d475..27c2e0d91322fa2314a4a091e8ecdd244dee7e02 100644
--- a/test/sophia/conflict.lua
+++ b/test/sophia/conflict.lua
@@ -17,6 +17,5 @@ function test_conflict()
 	fiber.sleep(0);
 
 	s:drop();
-	sophia_schedule();
 	return commits
 end
diff --git a/test/sophia/conflict.result b/test/sophia/conflict.result
index c58114ceee84ad5916dc7f57f8e24b870d40d486..35b8d7ea6aba289ed00062da85d6d20e9cd5e326 100644
--- a/test/sophia/conflict.result
+++ b/test/sophia/conflict.result
@@ -3,5 +3,5 @@ dofile('conflict.lua')
 ...
 test_conflict()
 ---
-- 1
+- 2
 ...
diff --git a/test/sophia/crud.result b/test/sophia/crud.result
deleted file mode 100644
index 4d43a559a8922a8fd4ff75d61ba44e67fa0712d6..0000000000000000000000000000000000000000
--- a/test/sophia/crud.result
+++ /dev/null
@@ -1,1178 +0,0 @@
--- insert
-space = box.schema.space.create('test', { engine = 'sophia' })
----
-...
-index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
----
-...
-sophia_dir()[1]
----
-- 1
-...
-for key = 1, 132 do space:insert({key}) end
----
-...
-t = {}
----
-...
-for key = 1, 132 do table.insert(t, space:get({key})) end
----
-...
-t
----
-- - [1]
-  - [2]
-  - [3]
-  - [4]
-  - [5]
-  - [6]
-  - [7]
-  - [8]
-  - [9]
-  - [10]
-  - [11]
-  - [12]
-  - [13]
-  - [14]
-  - [15]
-  - [16]
-  - [17]
-  - [18]
-  - [19]
-  - [20]
-  - [21]
-  - [22]
-  - [23]
-  - [24]
-  - [25]
-  - [26]
-  - [27]
-  - [28]
-  - [29]
-  - [30]
-  - [31]
-  - [32]
-  - [33]
-  - [34]
-  - [35]
-  - [36]
-  - [37]
-  - [38]
-  - [39]
-  - [40]
-  - [41]
-  - [42]
-  - [43]
-  - [44]
-  - [45]
-  - [46]
-  - [47]
-  - [48]
-  - [49]
-  - [50]
-  - [51]
-  - [52]
-  - [53]
-  - [54]
-  - [55]
-  - [56]
-  - [57]
-  - [58]
-  - [59]
-  - [60]
-  - [61]
-  - [62]
-  - [63]
-  - [64]
-  - [65]
-  - [66]
-  - [67]
-  - [68]
-  - [69]
-  - [70]
-  - [71]
-  - [72]
-  - [73]
-  - [74]
-  - [75]
-  - [76]
-  - [77]
-  - [78]
-  - [79]
-  - [80]
-  - [81]
-  - [82]
-  - [83]
-  - [84]
-  - [85]
-  - [86]
-  - [87]
-  - [88]
-  - [89]
-  - [90]
-  - [91]
-  - [92]
-  - [93]
-  - [94]
-  - [95]
-  - [96]
-  - [97]
-  - [98]
-  - [99]
-  - [100]
-  - [101]
-  - [102]
-  - [103]
-  - [104]
-  - [105]
-  - [106]
-  - [107]
-  - [108]
-  - [109]
-  - [110]
-  - [111]
-  - [112]
-  - [113]
-  - [114]
-  - [115]
-  - [116]
-  - [117]
-  - [118]
-  - [119]
-  - [120]
-  - [121]
-  - [122]
-  - [123]
-  - [124]
-  - [125]
-  - [126]
-  - [127]
-  - [128]
-  - [129]
-  - [130]
-  - [131]
-  - [132]
-...
--- replace/get
-for key = 1, 132 do space:replace({key, key}) end
----
-...
-t = {}
----
-...
-for key = 1, 132 do table.insert(t, space:get({key})) end
----
-...
-t
----
-- - [1, 1]
-  - [2, 2]
-  - [3, 3]
-  - [4, 4]
-  - [5, 5]
-  - [6, 6]
-  - [7, 7]
-  - [8, 8]
-  - [9, 9]
-  - [10, 10]
-  - [11, 11]
-  - [12, 12]
-  - [13, 13]
-  - [14, 14]
-  - [15, 15]
-  - [16, 16]
-  - [17, 17]
-  - [18, 18]
-  - [19, 19]
-  - [20, 20]
-  - [21, 21]
-  - [22, 22]
-  - [23, 23]
-  - [24, 24]
-  - [25, 25]
-  - [26, 26]
-  - [27, 27]
-  - [28, 28]
-  - [29, 29]
-  - [30, 30]
-  - [31, 31]
-  - [32, 32]
-  - [33, 33]
-  - [34, 34]
-  - [35, 35]
-  - [36, 36]
-  - [37, 37]
-  - [38, 38]
-  - [39, 39]
-  - [40, 40]
-  - [41, 41]
-  - [42, 42]
-  - [43, 43]
-  - [44, 44]
-  - [45, 45]
-  - [46, 46]
-  - [47, 47]
-  - [48, 48]
-  - [49, 49]
-  - [50, 50]
-  - [51, 51]
-  - [52, 52]
-  - [53, 53]
-  - [54, 54]
-  - [55, 55]
-  - [56, 56]
-  - [57, 57]
-  - [58, 58]
-  - [59, 59]
-  - [60, 60]
-  - [61, 61]
-  - [62, 62]
-  - [63, 63]
-  - [64, 64]
-  - [65, 65]
-  - [66, 66]
-  - [67, 67]
-  - [68, 68]
-  - [69, 69]
-  - [70, 70]
-  - [71, 71]
-  - [72, 72]
-  - [73, 73]
-  - [74, 74]
-  - [75, 75]
-  - [76, 76]
-  - [77, 77]
-  - [78, 78]
-  - [79, 79]
-  - [80, 80]
-  - [81, 81]
-  - [82, 82]
-  - [83, 83]
-  - [84, 84]
-  - [85, 85]
-  - [86, 86]
-  - [87, 87]
-  - [88, 88]
-  - [89, 89]
-  - [90, 90]
-  - [91, 91]
-  - [92, 92]
-  - [93, 93]
-  - [94, 94]
-  - [95, 95]
-  - [96, 96]
-  - [97, 97]
-  - [98, 98]
-  - [99, 99]
-  - [100, 100]
-  - [101, 101]
-  - [102, 102]
-  - [103, 103]
-  - [104, 104]
-  - [105, 105]
-  - [106, 106]
-  - [107, 107]
-  - [108, 108]
-  - [109, 109]
-  - [110, 110]
-  - [111, 111]
-  - [112, 112]
-  - [113, 113]
-  - [114, 114]
-  - [115, 115]
-  - [116, 116]
-  - [117, 117]
-  - [118, 118]
-  - [119, 119]
-  - [120, 120]
-  - [121, 121]
-  - [122, 122]
-  - [123, 123]
-  - [124, 124]
-  - [125, 125]
-  - [126, 126]
-  - [127, 127]
-  - [128, 128]
-  - [129, 129]
-  - [130, 130]
-  - [131, 131]
-  - [132, 132]
-...
--- update/get
-for key = 1, 132 do space:update({key}, {{'+', 2, key}}) end
----
-...
-t = {}
----
-...
-for key = 1, 132 do table.insert(t, space:get({key})) end
----
-...
-t
----
-- - [1, 2]
-  - [2, 4]
-  - [3, 6]
-  - [4, 8]
-  - [5, 10]
-  - [6, 12]
-  - [7, 14]
-  - [8, 16]
-  - [9, 18]
-  - [10, 20]
-  - [11, 22]
-  - [12, 24]
-  - [13, 26]
-  - [14, 28]
-  - [15, 30]
-  - [16, 32]
-  - [17, 34]
-  - [18, 36]
-  - [19, 38]
-  - [20, 40]
-  - [21, 42]
-  - [22, 44]
-  - [23, 46]
-  - [24, 48]
-  - [25, 50]
-  - [26, 52]
-  - [27, 54]
-  - [28, 56]
-  - [29, 58]
-  - [30, 60]
-  - [31, 62]
-  - [32, 64]
-  - [33, 66]
-  - [34, 68]
-  - [35, 70]
-  - [36, 72]
-  - [37, 74]
-  - [38, 76]
-  - [39, 78]
-  - [40, 80]
-  - [41, 82]
-  - [42, 84]
-  - [43, 86]
-  - [44, 88]
-  - [45, 90]
-  - [46, 92]
-  - [47, 94]
-  - [48, 96]
-  - [49, 98]
-  - [50, 100]
-  - [51, 102]
-  - [52, 104]
-  - [53, 106]
-  - [54, 108]
-  - [55, 110]
-  - [56, 112]
-  - [57, 114]
-  - [58, 116]
-  - [59, 118]
-  - [60, 120]
-  - [61, 122]
-  - [62, 124]
-  - [63, 126]
-  - [64, 128]
-  - [65, 130]
-  - [66, 132]
-  - [67, 134]
-  - [68, 136]
-  - [69, 138]
-  - [70, 140]
-  - [71, 142]
-  - [72, 144]
-  - [73, 146]
-  - [74, 148]
-  - [75, 150]
-  - [76, 152]
-  - [77, 154]
-  - [78, 156]
-  - [79, 158]
-  - [80, 160]
-  - [81, 162]
-  - [82, 164]
-  - [83, 166]
-  - [84, 168]
-  - [85, 170]
-  - [86, 172]
-  - [87, 174]
-  - [88, 176]
-  - [89, 178]
-  - [90, 180]
-  - [91, 182]
-  - [92, 184]
-  - [93, 186]
-  - [94, 188]
-  - [95, 190]
-  - [96, 192]
-  - [97, 194]
-  - [98, 196]
-  - [99, 198]
-  - [100, 200]
-  - [101, 202]
-  - [102, 204]
-  - [103, 206]
-  - [104, 208]
-  - [105, 210]
-  - [106, 212]
-  - [107, 214]
-  - [108, 216]
-  - [109, 218]
-  - [110, 220]
-  - [111, 222]
-  - [112, 224]
-  - [113, 226]
-  - [114, 228]
-  - [115, 230]
-  - [116, 232]
-  - [117, 234]
-  - [118, 236]
-  - [119, 238]
-  - [120, 240]
-  - [121, 242]
-  - [122, 244]
-  - [123, 246]
-  - [124, 248]
-  - [125, 250]
-  - [126, 252]
-  - [127, 254]
-  - [128, 256]
-  - [129, 258]
-  - [130, 260]
-  - [131, 262]
-  - [132, 264]
-...
--- delete/get
-for key = 1, 132 do space:delete({key}) end
----
-...
-for key = 1, 132 do assert(space:get({key}) == nil) end
----
-...
--- delete nonexistent
-space:delete({1234})
----
-...
--- select
-for key = 1, 96 do space:insert({key}) end
----
-...
-index = space.index[0]
----
-...
-index:select({}, {iterator = box.index.ALL})
----
-- - [1]
-  - [2]
-  - [3]
-  - [4]
-  - [5]
-  - [6]
-  - [7]
-  - [8]
-  - [9]
-  - [10]
-  - [11]
-  - [12]
-  - [13]
-  - [14]
-  - [15]
-  - [16]
-  - [17]
-  - [18]
-  - [19]
-  - [20]
-  - [21]
-  - [22]
-  - [23]
-  - [24]
-  - [25]
-  - [26]
-  - [27]
-  - [28]
-  - [29]
-  - [30]
-  - [31]
-  - [32]
-  - [33]
-  - [34]
-  - [35]
-  - [36]
-  - [37]
-  - [38]
-  - [39]
-  - [40]
-  - [41]
-  - [42]
-  - [43]
-  - [44]
-  - [45]
-  - [46]
-  - [47]
-  - [48]
-  - [49]
-  - [50]
-  - [51]
-  - [52]
-  - [53]
-  - [54]
-  - [55]
-  - [56]
-  - [57]
-  - [58]
-  - [59]
-  - [60]
-  - [61]
-  - [62]
-  - [63]
-  - [64]
-  - [65]
-  - [66]
-  - [67]
-  - [68]
-  - [69]
-  - [70]
-  - [71]
-  - [72]
-  - [73]
-  - [74]
-  - [75]
-  - [76]
-  - [77]
-  - [78]
-  - [79]
-  - [80]
-  - [81]
-  - [82]
-  - [83]
-  - [84]
-  - [85]
-  - [86]
-  - [87]
-  - [88]
-  - [89]
-  - [90]
-  - [91]
-  - [92]
-  - [93]
-  - [94]
-  - [95]
-  - [96]
-...
-index:select({}, {iterator = box.index.GE})
----
-- - [1]
-  - [2]
-  - [3]
-  - [4]
-  - [5]
-  - [6]
-  - [7]
-  - [8]
-  - [9]
-  - [10]
-  - [11]
-  - [12]
-  - [13]
-  - [14]
-  - [15]
-  - [16]
-  - [17]
-  - [18]
-  - [19]
-  - [20]
-  - [21]
-  - [22]
-  - [23]
-  - [24]
-  - [25]
-  - [26]
-  - [27]
-  - [28]
-  - [29]
-  - [30]
-  - [31]
-  - [32]
-  - [33]
-  - [34]
-  - [35]
-  - [36]
-  - [37]
-  - [38]
-  - [39]
-  - [40]
-  - [41]
-  - [42]
-  - [43]
-  - [44]
-  - [45]
-  - [46]
-  - [47]
-  - [48]
-  - [49]
-  - [50]
-  - [51]
-  - [52]
-  - [53]
-  - [54]
-  - [55]
-  - [56]
-  - [57]
-  - [58]
-  - [59]
-  - [60]
-  - [61]
-  - [62]
-  - [63]
-  - [64]
-  - [65]
-  - [66]
-  - [67]
-  - [68]
-  - [69]
-  - [70]
-  - [71]
-  - [72]
-  - [73]
-  - [74]
-  - [75]
-  - [76]
-  - [77]
-  - [78]
-  - [79]
-  - [80]
-  - [81]
-  - [82]
-  - [83]
-  - [84]
-  - [85]
-  - [86]
-  - [87]
-  - [88]
-  - [89]
-  - [90]
-  - [91]
-  - [92]
-  - [93]
-  - [94]
-  - [95]
-  - [96]
-...
-index:select(4,  {iterator = box.index.GE})
----
-- - [4]
-  - [5]
-  - [6]
-  - [7]
-  - [8]
-  - [9]
-  - [10]
-  - [11]
-  - [12]
-  - [13]
-  - [14]
-  - [15]
-  - [16]
-  - [17]
-  - [18]
-  - [19]
-  - [20]
-  - [21]
-  - [22]
-  - [23]
-  - [24]
-  - [25]
-  - [26]
-  - [27]
-  - [28]
-  - [29]
-  - [30]
-  - [31]
-  - [32]
-  - [33]
-  - [34]
-  - [35]
-  - [36]
-  - [37]
-  - [38]
-  - [39]
-  - [40]
-  - [41]
-  - [42]
-  - [43]
-  - [44]
-  - [45]
-  - [46]
-  - [47]
-  - [48]
-  - [49]
-  - [50]
-  - [51]
-  - [52]
-  - [53]
-  - [54]
-  - [55]
-  - [56]
-  - [57]
-  - [58]
-  - [59]
-  - [60]
-  - [61]
-  - [62]
-  - [63]
-  - [64]
-  - [65]
-  - [66]
-  - [67]
-  - [68]
-  - [69]
-  - [70]
-  - [71]
-  - [72]
-  - [73]
-  - [74]
-  - [75]
-  - [76]
-  - [77]
-  - [78]
-  - [79]
-  - [80]
-  - [81]
-  - [82]
-  - [83]
-  - [84]
-  - [85]
-  - [86]
-  - [87]
-  - [88]
-  - [89]
-  - [90]
-  - [91]
-  - [92]
-  - [93]
-  - [94]
-  - [95]
-  - [96]
-...
-index:select({}, {iterator = box.index.GT})
----
-- - [1]
-  - [2]
-  - [3]
-  - [4]
-  - [5]
-  - [6]
-  - [7]
-  - [8]
-  - [9]
-  - [10]
-  - [11]
-  - [12]
-  - [13]
-  - [14]
-  - [15]
-  - [16]
-  - [17]
-  - [18]
-  - [19]
-  - [20]
-  - [21]
-  - [22]
-  - [23]
-  - [24]
-  - [25]
-  - [26]
-  - [27]
-  - [28]
-  - [29]
-  - [30]
-  - [31]
-  - [32]
-  - [33]
-  - [34]
-  - [35]
-  - [36]
-  - [37]
-  - [38]
-  - [39]
-  - [40]
-  - [41]
-  - [42]
-  - [43]
-  - [44]
-  - [45]
-  - [46]
-  - [47]
-  - [48]
-  - [49]
-  - [50]
-  - [51]
-  - [52]
-  - [53]
-  - [54]
-  - [55]
-  - [56]
-  - [57]
-  - [58]
-  - [59]
-  - [60]
-  - [61]
-  - [62]
-  - [63]
-  - [64]
-  - [65]
-  - [66]
-  - [67]
-  - [68]
-  - [69]
-  - [70]
-  - [71]
-  - [72]
-  - [73]
-  - [74]
-  - [75]
-  - [76]
-  - [77]
-  - [78]
-  - [79]
-  - [80]
-  - [81]
-  - [82]
-  - [83]
-  - [84]
-  - [85]
-  - [86]
-  - [87]
-  - [88]
-  - [89]
-  - [90]
-  - [91]
-  - [92]
-  - [93]
-  - [94]
-  - [95]
-  - [96]
-...
-index:select(4,  {iterator = box.index.GT})
----
-- - [5]
-  - [6]
-  - [7]
-  - [8]
-  - [9]
-  - [10]
-  - [11]
-  - [12]
-  - [13]
-  - [14]
-  - [15]
-  - [16]
-  - [17]
-  - [18]
-  - [19]
-  - [20]
-  - [21]
-  - [22]
-  - [23]
-  - [24]
-  - [25]
-  - [26]
-  - [27]
-  - [28]
-  - [29]
-  - [30]
-  - [31]
-  - [32]
-  - [33]
-  - [34]
-  - [35]
-  - [36]
-  - [37]
-  - [38]
-  - [39]
-  - [40]
-  - [41]
-  - [42]
-  - [43]
-  - [44]
-  - [45]
-  - [46]
-  - [47]
-  - [48]
-  - [49]
-  - [50]
-  - [51]
-  - [52]
-  - [53]
-  - [54]
-  - [55]
-  - [56]
-  - [57]
-  - [58]
-  - [59]
-  - [60]
-  - [61]
-  - [62]
-  - [63]
-  - [64]
-  - [65]
-  - [66]
-  - [67]
-  - [68]
-  - [69]
-  - [70]
-  - [71]
-  - [72]
-  - [73]
-  - [74]
-  - [75]
-  - [76]
-  - [77]
-  - [78]
-  - [79]
-  - [80]
-  - [81]
-  - [82]
-  - [83]
-  - [84]
-  - [85]
-  - [86]
-  - [87]
-  - [88]
-  - [89]
-  - [90]
-  - [91]
-  - [92]
-  - [93]
-  - [94]
-  - [95]
-  - [96]
-...
-index:select({}, {iterator = box.index.LE})
----
-- - [96]
-  - [95]
-  - [94]
-  - [93]
-  - [92]
-  - [91]
-  - [90]
-  - [89]
-  - [88]
-  - [87]
-  - [86]
-  - [85]
-  - [84]
-  - [83]
-  - [82]
-  - [81]
-  - [80]
-  - [79]
-  - [78]
-  - [77]
-  - [76]
-  - [75]
-  - [74]
-  - [73]
-  - [72]
-  - [71]
-  - [70]
-  - [69]
-  - [68]
-  - [67]
-  - [66]
-  - [65]
-  - [64]
-  - [63]
-  - [62]
-  - [61]
-  - [60]
-  - [59]
-  - [58]
-  - [57]
-  - [56]
-  - [55]
-  - [54]
-  - [53]
-  - [52]
-  - [51]
-  - [50]
-  - [49]
-  - [48]
-  - [47]
-  - [46]
-  - [45]
-  - [44]
-  - [43]
-  - [42]
-  - [41]
-  - [40]
-  - [39]
-  - [38]
-  - [37]
-  - [36]
-  - [35]
-  - [34]
-  - [33]
-  - [32]
-  - [31]
-  - [30]
-  - [29]
-  - [28]
-  - [27]
-  - [26]
-  - [25]
-  - [24]
-  - [23]
-  - [22]
-  - [21]
-  - [20]
-  - [19]
-  - [18]
-  - [17]
-  - [16]
-  - [15]
-  - [14]
-  - [13]
-  - [12]
-  - [11]
-  - [10]
-  - [9]
-  - [8]
-  - [7]
-  - [6]
-  - [5]
-  - [4]
-  - [3]
-  - [2]
-  - [1]
-...
-index:select(7,  {iterator = box.index.LE})
----
-- - [7]
-  - [6]
-  - [5]
-  - [4]
-  - [3]
-  - [2]
-  - [1]
-...
-index:select({}, {iterator = box.index.LT})
----
-- - [96]
-  - [95]
-  - [94]
-  - [93]
-  - [92]
-  - [91]
-  - [90]
-  - [89]
-  - [88]
-  - [87]
-  - [86]
-  - [85]
-  - [84]
-  - [83]
-  - [82]
-  - [81]
-  - [80]
-  - [79]
-  - [78]
-  - [77]
-  - [76]
-  - [75]
-  - [74]
-  - [73]
-  - [72]
-  - [71]
-  - [70]
-  - [69]
-  - [68]
-  - [67]
-  - [66]
-  - [65]
-  - [64]
-  - [63]
-  - [62]
-  - [61]
-  - [60]
-  - [59]
-  - [58]
-  - [57]
-  - [56]
-  - [55]
-  - [54]
-  - [53]
-  - [52]
-  - [51]
-  - [50]
-  - [49]
-  - [48]
-  - [47]
-  - [46]
-  - [45]
-  - [44]
-  - [43]
-  - [42]
-  - [41]
-  - [40]
-  - [39]
-  - [38]
-  - [37]
-  - [36]
-  - [35]
-  - [34]
-  - [33]
-  - [32]
-  - [31]
-  - [30]
-  - [29]
-  - [28]
-  - [27]
-  - [26]
-  - [25]
-  - [24]
-  - [23]
-  - [22]
-  - [21]
-  - [20]
-  - [19]
-  - [18]
-  - [17]
-  - [16]
-  - [15]
-  - [14]
-  - [13]
-  - [12]
-  - [11]
-  - [10]
-  - [9]
-  - [8]
-  - [7]
-  - [6]
-  - [5]
-  - [4]
-  - [3]
-  - [2]
-  - [1]
-...
-index:select(7,  {iterator = box.index.LT})
----
-- - [6]
-  - [5]
-  - [4]
-  - [3]
-  - [2]
-  - [1]
-...
-space:drop()
----
-...
-sophia_schedule()
----
-...
-sophia_dir()[1]
----
-- 0
-...
diff --git a/test/sophia/crud.test.lua b/test/sophia/crud.test.lua
deleted file mode 100644
index 9b48c4339f068a308158ef72fe4ee425d4b1c287..0000000000000000000000000000000000000000
--- a/test/sophia/crud.test.lua
+++ /dev/null
@@ -1,50 +0,0 @@
-
--- insert
-
-space = box.schema.space.create('test', { engine = 'sophia' })
-index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
-sophia_dir()[1]
-for key = 1, 132 do space:insert({key}) end
-t = {}
-for key = 1, 132 do table.insert(t, space:get({key})) end
-t
-
--- replace/get
-
-for key = 1, 132 do space:replace({key, key}) end
-t = {}
-for key = 1, 132 do table.insert(t, space:get({key})) end
-t
-
--- update/get
-
-for key = 1, 132 do space:update({key}, {{'+', 2, key}}) end
-t = {}
-for key = 1, 132 do table.insert(t, space:get({key})) end
-t
-
--- delete/get
-
-for key = 1, 132 do space:delete({key}) end
-for key = 1, 132 do assert(space:get({key}) == nil) end
-
--- delete nonexistent
-space:delete({1234})
-
--- select
-
-for key = 1, 96 do space:insert({key}) end
-index = space.index[0]
-index:select({}, {iterator = box.index.ALL})
-index:select({}, {iterator = box.index.GE})
-index:select(4,  {iterator = box.index.GE})
-index:select({}, {iterator = box.index.GT})
-index:select(4,  {iterator = box.index.GT})
-index:select({}, {iterator = box.index.LE})
-index:select(7,  {iterator = box.index.LE})
-index:select({}, {iterator = box.index.LT})
-index:select(7,  {iterator = box.index.LT})
-
-space:drop()
-sophia_schedule()
-sophia_dir()[1]
diff --git a/test/sophia/ddl.result b/test/sophia/ddl.result
index 3c1c14425b2b6bb4d429d13d43dc01ce0fc2e721..74bbccf5f0d745640c494583857113ada4c54758 100644
--- a/test/sophia/ddl.result
+++ b/test/sophia/ddl.result
@@ -2,52 +2,26 @@
 space = box.schema.space.create('test', { engine = 'sophia' })
 ---
 ...
-sophia_dir()[1]
----
-- 0
-...
 space:drop()
 ---
 ...
-sophia_schedule()
----
-...
-sophia_dir()[1]
----
-- 0
-...
--- index create/drop
+-- space index create/drop
 space = box.schema.space.create('test', { engine = 'sophia' })
 ---
 ...
 index = space:create_index('primary')
 ---
 ...
-sophia_dir()[1]
----
-- 1
-...
 space:drop()
 ---
 ...
-sophia_schedule()
----
-...
-sophia_dir()[1]
----
-- 0
-...
--- index create/drop alter
+-- space index create/drop alter
 space = box.schema.space.create('test', { engine = 'sophia' })
 ---
 ...
 index = space:create_index('primary')
 ---
 ...
-sophia_dir()[1]
----
-- 1
-...
 _index = box.space[box.schema.INDEX_ID]
 ---
 ...
@@ -57,14 +31,7 @@ _index:delete{102, 0}
 space:drop()
 ---
 ...
-sophia_schedule()
----
-...
-sophia_dir()[1]
----
-- 0
-...
--- index create/drop tree string
+-- space index create/drop tree string
 space = box.schema.space.create('test', { engine = 'sophia' })
 ---
 ...
@@ -73,58 +40,73 @@ index = space:create_index('primary', {type = 'tree', parts = {1, 'STR'}})
 ...
 space:insert({'test'})
 ---
-- ['test']
 ...
 space:drop()
 ---
 ...
-sophia_schedule()
+-- space index create/drop tree num
+space = box.schema.space.create('test', { engine = 'sophia' })
 ---
 ...
-sophia_dir()[1]
+index = space:create_index('primary', {type = 'tree', parts = {1, 'num'}})
+---
+...
+space:insert({13})
+---
+...
+space:drop()
 ---
-- 0
 ...
--- index create/drop tree num
+-- space index create/drop tree multi-part num
 space = box.schema.space.create('test', { engine = 'sophia' })
 ---
 ...
-index = space:create_index('primary', {type = 'tree', parts = {1, 'num'}})
+index = space:create_index('primary', {type = 'tree', parts = {1, 'num', 2, 'num'}})
 ---
 ...
 space:insert({13})
 ---
-- [13]
+- error: Tuple field count 1 is less than required by a defined index (expected 2)
 ...
 space:drop()
 ---
 ...
-sophia_schedule()
+-- space index create/drop tree incorrect key pos
+space = box.schema.space.create('test', { engine = 'sophia' })
 ---
 ...
-sophia_dir()[1]
+index = space:create_index('primary', {type = 'tree', parts = {3, 'num'}})
 ---
-- 0
+- error: 'Can''t create or modify index ''primary'' in space ''test'': Sophia TREE
+    key-parts must follow first and cannot be sparse'
 ...
--- index create hash 
+space:drop()
+---
+...
+-- space index create/drop tree sparse
 space = box.schema.space.create('test', { engine = 'sophia' })
 ---
 ...
-index = space:create_index('primary', {type = 'hash'})
+index = space:create_index('primary', {type = 'tree', parts = {1, 'num', 3, 'num'}})
 ---
-- error: Unsupported index type supplied for index 'primary' in space 'test'
+- error: 'Can''t create or modify index ''primary'' in space ''test'': Sophia TREE
+    key-parts must follow first and cannot be sparse'
 ...
 space:drop()
 ---
 ...
-sophia_schedule()
+-- space index create hash
+space = box.schema.space.create('test', { engine = 'sophia' })
 ---
 ...
-sophia_dir()[1]
+index = space:create_index('primary', {type = 'hash'})
+---
+- error: Unsupported index type supplied for index 'primary' in space 'test'
+...
+space:drop()
 ---
-- 0
 ...
--- secondary index create
+-- space secondary index create
 space = box.schema.space.create('test', { engine = 'sophia' })
 ---
 ...
@@ -139,14 +121,7 @@ index2 = space:create_index('secondary')
 space:drop()
 ---
 ...
-sophia_schedule()
----
-...
-sophia_dir()[1]
----
-- 0
-...
--- index size
+-- space index size
 space = box.schema.space.create('test', { engine = 'sophia' })
 ---
 ...
@@ -162,15 +137,12 @@ primary:len()
 ...
 space:insert({13})
 ---
-- [13]
 ...
 space:insert({14})
 ---
-- [14]
 ...
 space:insert({15})
 ---
-- [15]
 ...
 primary:len()
 ---
@@ -179,6 +151,17 @@ primary:len()
 space:drop()
 ---
 ...
-sophia_schedule()
+-- ensure alter is not supported
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary')
+---
+...
+index:alter({parts={1,'NUM'}})
+---
+- error: alter is not supported for a Sophia index
+...
+space:drop()
 ---
 ...
diff --git a/test/sophia/ddl.test.lua b/test/sophia/ddl.test.lua
index f6a699f9d7537308fea31d1ed096e94f4748d0aa..6e8de6660d442f68ec73f1a41a003d19234fe608 100644
--- a/test/sophia/ddl.test.lua
+++ b/test/sophia/ddl.test.lua
@@ -1,69 +1,70 @@
 
 -- space create/drop
-
 space = box.schema.space.create('test', { engine = 'sophia' })
-sophia_dir()[1]
 space:drop()
-sophia_schedule()
-sophia_dir()[1]
 
--- index create/drop
 
+-- space index create/drop
 space = box.schema.space.create('test', { engine = 'sophia' })
 index = space:create_index('primary')
-sophia_dir()[1]
 space:drop()
-sophia_schedule()
-sophia_dir()[1]
 
--- index create/drop alter
 
+-- space index create/drop alter
 space = box.schema.space.create('test', { engine = 'sophia' })
 index = space:create_index('primary')
-sophia_dir()[1]
 _index = box.space[box.schema.INDEX_ID]
 _index:delete{102, 0}
 space:drop()
-sophia_schedule()
-sophia_dir()[1]
 
--- index create/drop tree string
 
+-- space index create/drop tree string
 space = box.schema.space.create('test', { engine = 'sophia' })
 index = space:create_index('primary', {type = 'tree', parts = {1, 'STR'}})
 space:insert({'test'})
 space:drop()
-sophia_schedule()
-sophia_dir()[1]
 
--- index create/drop tree num
 
+-- space index create/drop tree num
 space = box.schema.space.create('test', { engine = 'sophia' })
 index = space:create_index('primary', {type = 'tree', parts = {1, 'num'}})
 space:insert({13})
 space:drop()
-sophia_schedule()
-sophia_dir()[1]
 
--- index create hash 
 
+-- space index create/drop tree multi-part num
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', {type = 'tree', parts = {1, 'num', 2, 'num'}})
+space:insert({13})
+space:drop()
+
+
+-- space index create/drop tree incorrect key pos
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', {type = 'tree', parts = {3, 'num'}})
+space:drop()
+
+
+-- space index create/drop tree sparse
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', {type = 'tree', parts = {1, 'num', 3, 'num'}})
+space:drop()
+
+
+-- space index create hash
 space = box.schema.space.create('test', { engine = 'sophia' })
 index = space:create_index('primary', {type = 'hash'})
 space:drop()
-sophia_schedule()
-sophia_dir()[1]
 
--- secondary index create
 
+-- space secondary index create
 space = box.schema.space.create('test', { engine = 'sophia' })
 index1 = space:create_index('primary')
 index2 = space:create_index('secondary')
 space:drop()
-sophia_schedule()
-sophia_dir()[1]
 
--- index size
 
+-- space index size
 space = box.schema.space.create('test', { engine = 'sophia' })
 index = space:create_index('primary')
 primary = space.index[0]
@@ -73,4 +74,10 @@ space:insert({14})
 space:insert({15})
 primary:len()
 space:drop()
-sophia_schedule()
+
+
+-- ensure alter is not supported
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary')
+index:alter({parts={1,'NUM'}})
+space:drop()
diff --git a/test/sophia/delete.result b/test/sophia/delete.result
new file mode 100644
index 0000000000000000000000000000000000000000..d264b0eab374a327fbbf225cd7cba92d8d5791a6
--- /dev/null
+++ b/test/sophia/delete.result
@@ -0,0 +1,393 @@
+-- delete (str)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'str'} })
+---
+...
+for key = 1, 100 do space:replace({tostring(key)}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({tostring(key)})) end
+---
+...
+t
+---
+- - ['1']
+  - ['2']
+  - ['3']
+  - ['4']
+  - ['5']
+  - ['6']
+  - ['7']
+  - ['8']
+  - ['9']
+  - ['10']
+  - ['11']
+  - ['12']
+  - ['13']
+  - ['14']
+  - ['15']
+  - ['16']
+  - ['17']
+  - ['18']
+  - ['19']
+  - ['20']
+  - ['21']
+  - ['22']
+  - ['23']
+  - ['24']
+  - ['25']
+  - ['26']
+  - ['27']
+  - ['28']
+  - ['29']
+  - ['30']
+  - ['31']
+  - ['32']
+  - ['33']
+  - ['34']
+  - ['35']
+  - ['36']
+  - ['37']
+  - ['38']
+  - ['39']
+  - ['40']
+  - ['41']
+  - ['42']
+  - ['43']
+  - ['44']
+  - ['45']
+  - ['46']
+  - ['47']
+  - ['48']
+  - ['49']
+  - ['50']
+  - ['51']
+  - ['52']
+  - ['53']
+  - ['54']
+  - ['55']
+  - ['56']
+  - ['57']
+  - ['58']
+  - ['59']
+  - ['60']
+  - ['61']
+  - ['62']
+  - ['63']
+  - ['64']
+  - ['65']
+  - ['66']
+  - ['67']
+  - ['68']
+  - ['69']
+  - ['70']
+  - ['71']
+  - ['72']
+  - ['73']
+  - ['74']
+  - ['75']
+  - ['76']
+  - ['77']
+  - ['78']
+  - ['79']
+  - ['80']
+  - ['81']
+  - ['82']
+  - ['83']
+  - ['84']
+  - ['85']
+  - ['86']
+  - ['87']
+  - ['88']
+  - ['89']
+  - ['90']
+  - ['91']
+  - ['92']
+  - ['93']
+  - ['94']
+  - ['95']
+  - ['96']
+  - ['97']
+  - ['98']
+  - ['99']
+  - ['100']
+...
+for key = 1, 100 do space:delete({tostring(key)}) end
+---
+...
+for key = 1, 100 do assert(space:get({tostring(key)}) == nil) end
+---
+...
+space:delete({tostring(7)})
+---
+...
+space:drop()
+---
+...
+-- delete (num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
+---
+...
+for key = 1, 100 do space:replace({key}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({key})) end
+---
+...
+t
+---
+- - [1]
+  - [2]
+  - [3]
+  - [4]
+  - [5]
+  - [6]
+  - [7]
+  - [8]
+  - [9]
+  - [10]
+  - [11]
+  - [12]
+  - [13]
+  - [14]
+  - [15]
+  - [16]
+  - [17]
+  - [18]
+  - [19]
+  - [20]
+  - [21]
+  - [22]
+  - [23]
+  - [24]
+  - [25]
+  - [26]
+  - [27]
+  - [28]
+  - [29]
+  - [30]
+  - [31]
+  - [32]
+  - [33]
+  - [34]
+  - [35]
+  - [36]
+  - [37]
+  - [38]
+  - [39]
+  - [40]
+  - [41]
+  - [42]
+  - [43]
+  - [44]
+  - [45]
+  - [46]
+  - [47]
+  - [48]
+  - [49]
+  - [50]
+  - [51]
+  - [52]
+  - [53]
+  - [54]
+  - [55]
+  - [56]
+  - [57]
+  - [58]
+  - [59]
+  - [60]
+  - [61]
+  - [62]
+  - [63]
+  - [64]
+  - [65]
+  - [66]
+  - [67]
+  - [68]
+  - [69]
+  - [70]
+  - [71]
+  - [72]
+  - [73]
+  - [74]
+  - [75]
+  - [76]
+  - [77]
+  - [78]
+  - [79]
+  - [80]
+  - [81]
+  - [82]
+  - [83]
+  - [84]
+  - [85]
+  - [86]
+  - [87]
+  - [88]
+  - [89]
+  - [90]
+  - [91]
+  - [92]
+  - [93]
+  - [94]
+  - [95]
+  - [96]
+  - [97]
+  - [98]
+  - [99]
+  - [100]
+...
+for key = 1, 100 do space:delete({key}) end
+---
+...
+for key = 1, 100 do assert(space:get({key}) == nil) end
+---
+...
+space:delete({7})
+---
+...
+space:drop()
+---
+...
+-- delete multi-part (num, num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num', 2, 'num'} })
+---
+...
+for key = 1, 100 do space:replace({key, key}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({key, key})) end
+---
+...
+t
+---
+- - [1, 1]
+  - [2, 2]
+  - [3, 3]
+  - [4, 4]
+  - [5, 5]
+  - [6, 6]
+  - [7, 7]
+  - [8, 8]
+  - [9, 9]
+  - [10, 10]
+  - [11, 11]
+  - [12, 12]
+  - [13, 13]
+  - [14, 14]
+  - [15, 15]
+  - [16, 16]
+  - [17, 17]
+  - [18, 18]
+  - [19, 19]
+  - [20, 20]
+  - [21, 21]
+  - [22, 22]
+  - [23, 23]
+  - [24, 24]
+  - [25, 25]
+  - [26, 26]
+  - [27, 27]
+  - [28, 28]
+  - [29, 29]
+  - [30, 30]
+  - [31, 31]
+  - [32, 32]
+  - [33, 33]
+  - [34, 34]
+  - [35, 35]
+  - [36, 36]
+  - [37, 37]
+  - [38, 38]
+  - [39, 39]
+  - [40, 40]
+  - [41, 41]
+  - [42, 42]
+  - [43, 43]
+  - [44, 44]
+  - [45, 45]
+  - [46, 46]
+  - [47, 47]
+  - [48, 48]
+  - [49, 49]
+  - [50, 50]
+  - [51, 51]
+  - [52, 52]
+  - [53, 53]
+  - [54, 54]
+  - [55, 55]
+  - [56, 56]
+  - [57, 57]
+  - [58, 58]
+  - [59, 59]
+  - [60, 60]
+  - [61, 61]
+  - [62, 62]
+  - [63, 63]
+  - [64, 64]
+  - [65, 65]
+  - [66, 66]
+  - [67, 67]
+  - [68, 68]
+  - [69, 69]
+  - [70, 70]
+  - [71, 71]
+  - [72, 72]
+  - [73, 73]
+  - [74, 74]
+  - [75, 75]
+  - [76, 76]
+  - [77, 77]
+  - [78, 78]
+  - [79, 79]
+  - [80, 80]
+  - [81, 81]
+  - [82, 82]
+  - [83, 83]
+  - [84, 84]
+  - [85, 85]
+  - [86, 86]
+  - [87, 87]
+  - [88, 88]
+  - [89, 89]
+  - [90, 90]
+  - [91, 91]
+  - [92, 92]
+  - [93, 93]
+  - [94, 94]
+  - [95, 95]
+  - [96, 96]
+  - [97, 97]
+  - [98, 98]
+  - [99, 99]
+  - [100, 100]
+...
+for key = 1, 100 do space:delete({key, key}) end
+---
+...
+for key = 1, 100 do assert(space:get({key, key}) == nil) end
+---
+...
+space:delete({7, 7})
+---
+...
+space:drop()
+---
+...
diff --git a/test/sophia/delete.test.lua b/test/sophia/delete.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..6749e2d4198f6651e439dcd334b479ff3d9e0284
--- /dev/null
+++ b/test/sophia/delete.test.lua
@@ -0,0 +1,39 @@
+
+-- delete (str)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'str'} })
+for key = 1, 100 do space:replace({tostring(key)}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({tostring(key)})) end
+t
+for key = 1, 100 do space:delete({tostring(key)}) end
+for key = 1, 100 do assert(space:get({tostring(key)}) == nil) end
+
+space:delete({tostring(7)})
+space:drop()
+
+
+-- delete (num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
+for key = 1, 100 do space:replace({key}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({key})) end
+t
+for key = 1, 100 do space:delete({key}) end
+for key = 1, 100 do assert(space:get({key}) == nil) end
+space:delete({7})
+space:drop()
+
+
+-- delete multi-part (num, num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num', 2, 'num'} })
+for key = 1, 100 do space:replace({key, key}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({key, key})) end
+t
+for key = 1, 100 do space:delete({key, key}) end
+for key = 1, 100 do assert(space:get({key, key}) == nil) end
+space:delete({7, 7})
+space:drop()
diff --git a/test/sophia/gh.result b/test/sophia/gh.result
index 0f91fd492e5a07289e5a33bcd9c1b70eb37ff606..3e46a144a956a327c8cbd57ea92bcaff2d0c11d3 100644
--- a/test/sophia/gh.result
+++ b/test/sophia/gh.result
@@ -1,4 +1,4 @@
--- gh-283: Sophia: hang after three creates and drops
+-- gh-283: hang after three creates and drops
 s = box.schema.space.create('space0', {engine='sophia'})
 ---
 ...
@@ -7,14 +7,10 @@ i = s:create_index('space0', {type = 'tree', parts = {1, 'STR'}})
 ...
 s:insert{'a', 'b', 'c'}
 ---
-- ['a', 'b', 'c']
 ...
 s:drop()
 ---
 ...
-sophia_schedule()
----
-...
 s = box.schema.space.create('space0', {engine='sophia'})
 ---
 ...
@@ -23,7 +19,6 @@ i = s:create_index('space0', {type = 'tree', parts = {1, 'STR'}})
 ...
 s:insert{'a', 'b', 'c'}
 ---
-- ['a', 'b', 'c']
 ...
 t = s.index[0]:select({}, {iterator = box.index.ALL})
 ---
@@ -35,9 +30,6 @@ t
 s:drop()
 ---
 ...
-sophia_schedule()
----
-...
 s = box.schema.space.create('space0', {engine='sophia'})
 ---
 ...
@@ -46,7 +38,6 @@ i = s:create_index('space0', {type = 'tree', parts = {1, 'STR'}})
 ...
 s:insert{'a', 'b', 'c'}
 ---
-- ['a', 'b', 'c']
 ...
 t = s.index[0]:select({}, {iterator = box.index.ALL})
 ---
@@ -58,10 +49,7 @@ t
 s:drop()
 ---
 ...
-sophia_schedule()
----
-...
--- gh-280: Sophia: crash if insert without index
+-- gh-280: crash if insert without index
 s = box.schema.space.create('test', {engine='sophia'})
 ---
 ...
@@ -72,15 +60,12 @@ s:insert{'a'}
 s:drop()
 ---
 ...
-sophia_schedule()
----
-...
 -- gh-436: No error when creating temporary sophia space
 s = box.schema.space.create('tester',{engine='sophia', temporary=true})
 ---
 - error: 'Can''t modify space ''tester'': space does not support temporary flag'
 ...
--- gh-432: Sophia: ignored limit
+-- gh-432: ignored limit
 s = box.schema.space.create('tester',{engine='sophia'})
 ---
 ...
@@ -108,9 +93,6 @@ t
 s:drop()
 ---
 ...
-sophia_schedule()
----
-...
 s = box.schema.space.create('tester', {engine='sophia'})
 ---
 ...
@@ -125,7 +107,7 @@ t = s:select({''},{iterator='GT', limit =1})
 ...
 t
 ---
-- - ['1']
+- - ['11']
 ...
 t = s:select({},{iterator='GT', limit =1})
 ---
@@ -137,48 +119,20 @@ t
 s:drop()
 ---
 ...
-sophia_schedule()
----
-...
--- gh-680: Sophia: assertion on update
-s = box.schema.space.create('tester', {engine='sophia'})
----
-...
-i = s:create_index('primary',{type = 'tree', parts = {2, 'STR'}})
----
-...
-s:insert{1,'X'}
----
-- [1, 'X']
-...
-s:update({'X'}, {{'=', 2, 'Y'}})
+-- gh-681: support or produce error on space::alter
+s = box.schema.space.create('M', {engine='sophia'})
 ---
-- error: Attempt to modify a tuple field which is part of index 'primary' in space
-    'tester'
 ...
-s:select{'X'}
+i = s:create_index('primary',{})
 ---
-- - [1, 'X']
 ...
-s:select{'Y'}
+s:insert{5}
 ---
-- []
 ...
-s:update({'X'}, {{'=', 3, 'Z'}})
+s.index.primary:alter({parts={1,'NUM'}})
 ---
-- [1, 'X', 'Z']
-...
-s:select{'X'}
----
-- - [1, 'X', 'Z']
-...
-s:select{'Y'}
----
-- []
+- error: alter is not supported for a Sophia index
 ...
 s:drop()
 ---
 ...
-sophia_schedule()
----
-...
diff --git a/test/sophia/gh.test.lua b/test/sophia/gh.test.lua
index 40698f195b7a3edbc0e49b922fcecda96b07db6d..3ef3f0b1a036df7f031763a9446bbf8d004fd9fb 100644
--- a/test/sophia/gh.test.lua
+++ b/test/sophia/gh.test.lua
@@ -1,11 +1,9 @@
 
--- gh-283: Sophia: hang after three creates and drops
-
+-- gh-283: hang after three creates and drops
 s = box.schema.space.create('space0', {engine='sophia'})
 i = s:create_index('space0', {type = 'tree', parts = {1, 'STR'}})
 s:insert{'a', 'b', 'c'}
 s:drop()
-sophia_schedule()
 
 s = box.schema.space.create('space0', {engine='sophia'})
 i = s:create_index('space0', {type = 'tree', parts = {1, 'STR'}})
@@ -13,7 +11,6 @@ s:insert{'a', 'b', 'c'}
 t = s.index[0]:select({}, {iterator = box.index.ALL})
 t
 s:drop()
-sophia_schedule()
 
 s = box.schema.space.create('space0', {engine='sophia'})
 i = s:create_index('space0', {type = 'tree', parts = {1, 'STR'}})
@@ -21,21 +18,19 @@ s:insert{'a', 'b', 'c'}
 t = s.index[0]:select({}, {iterator = box.index.ALL})
 t
 s:drop()
-sophia_schedule()
 
--- gh-280: Sophia: crash if insert without index
 
+-- gh-280: crash if insert without index
 s = box.schema.space.create('test', {engine='sophia'})
 s:insert{'a'}
 s:drop()
-sophia_schedule()
 
--- gh-436: No error when creating temporary sophia space
 
+-- gh-436: No error when creating temporary sophia space
 s = box.schema.space.create('tester',{engine='sophia', temporary=true})
 
--- gh-432: Sophia: ignored limit
 
+-- gh-432: ignored limit
 s = box.schema.space.create('tester',{engine='sophia'})
 i = s:create_index('sophia_index', {})
 for v=1, 100 do s:insert({v}) end
@@ -44,7 +39,6 @@ t
 t = s:select({},{iterator='GT', limit =1})
 t
 s:drop()
-sophia_schedule()
 
 s = box.schema.space.create('tester', {engine='sophia'})
 i = s:create_index('sophia_index', {type = 'tree', parts = {1, 'STR'}})
@@ -54,17 +48,11 @@ t
 t = s:select({},{iterator='GT', limit =1})
 t
 s:drop()
-sophia_schedule()
 
--- gh-680: Sophia: assertion on update
-s = box.schema.space.create('tester', {engine='sophia'})
-i = s:create_index('primary',{type = 'tree', parts = {2, 'STR'}})
-s:insert{1,'X'}
-s:update({'X'}, {{'=', 2, 'Y'}})
-s:select{'X'}
-s:select{'Y'}
-s:update({'X'}, {{'=', 3, 'Z'}})
-s:select{'X'}
-s:select{'Y'}
+
+-- gh-681: support or produce error on space::alter
+s = box.schema.space.create('M', {engine='sophia'})
+i = s:create_index('primary',{})
+s:insert{5}
+s.index.primary:alter({parts={1,'NUM'}})
 s:drop()
-sophia_schedule()
diff --git a/test/sophia/insert.result b/test/sophia/insert.result
new file mode 100644
index 0000000000000000000000000000000000000000..644199638e0cacc656ae59e0641b63d57c4d2b7b
--- /dev/null
+++ b/test/sophia/insert.result
@@ -0,0 +1,378 @@
+-- insert (str)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'str'} })
+---
+...
+for key = 1, 100 do space:insert({tostring(key)}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({tostring(key)})) end
+---
+...
+t
+---
+- - ['1']
+  - ['2']
+  - ['3']
+  - ['4']
+  - ['5']
+  - ['6']
+  - ['7']
+  - ['8']
+  - ['9']
+  - ['10']
+  - ['11']
+  - ['12']
+  - ['13']
+  - ['14']
+  - ['15']
+  - ['16']
+  - ['17']
+  - ['18']
+  - ['19']
+  - ['20']
+  - ['21']
+  - ['22']
+  - ['23']
+  - ['24']
+  - ['25']
+  - ['26']
+  - ['27']
+  - ['28']
+  - ['29']
+  - ['30']
+  - ['31']
+  - ['32']
+  - ['33']
+  - ['34']
+  - ['35']
+  - ['36']
+  - ['37']
+  - ['38']
+  - ['39']
+  - ['40']
+  - ['41']
+  - ['42']
+  - ['43']
+  - ['44']
+  - ['45']
+  - ['46']
+  - ['47']
+  - ['48']
+  - ['49']
+  - ['50']
+  - ['51']
+  - ['52']
+  - ['53']
+  - ['54']
+  - ['55']
+  - ['56']
+  - ['57']
+  - ['58']
+  - ['59']
+  - ['60']
+  - ['61']
+  - ['62']
+  - ['63']
+  - ['64']
+  - ['65']
+  - ['66']
+  - ['67']
+  - ['68']
+  - ['69']
+  - ['70']
+  - ['71']
+  - ['72']
+  - ['73']
+  - ['74']
+  - ['75']
+  - ['76']
+  - ['77']
+  - ['78']
+  - ['79']
+  - ['80']
+  - ['81']
+  - ['82']
+  - ['83']
+  - ['84']
+  - ['85']
+  - ['86']
+  - ['87']
+  - ['88']
+  - ['89']
+  - ['90']
+  - ['91']
+  - ['92']
+  - ['93']
+  - ['94']
+  - ['95']
+  - ['96']
+  - ['97']
+  - ['98']
+  - ['99']
+  - ['100']
+...
+space:insert({tostring(7)})
+---
+- error: Duplicate key exists in unique index 'primary' in space 'test'
+...
+space:drop()
+---
+...
+-- insert (num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
+---
+...
+for key = 1, 100 do space:insert({key}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({key})) end
+---
+...
+t
+---
+- - [1]
+  - [2]
+  - [3]
+  - [4]
+  - [5]
+  - [6]
+  - [7]
+  - [8]
+  - [9]
+  - [10]
+  - [11]
+  - [12]
+  - [13]
+  - [14]
+  - [15]
+  - [16]
+  - [17]
+  - [18]
+  - [19]
+  - [20]
+  - [21]
+  - [22]
+  - [23]
+  - [24]
+  - [25]
+  - [26]
+  - [27]
+  - [28]
+  - [29]
+  - [30]
+  - [31]
+  - [32]
+  - [33]
+  - [34]
+  - [35]
+  - [36]
+  - [37]
+  - [38]
+  - [39]
+  - [40]
+  - [41]
+  - [42]
+  - [43]
+  - [44]
+  - [45]
+  - [46]
+  - [47]
+  - [48]
+  - [49]
+  - [50]
+  - [51]
+  - [52]
+  - [53]
+  - [54]
+  - [55]
+  - [56]
+  - [57]
+  - [58]
+  - [59]
+  - [60]
+  - [61]
+  - [62]
+  - [63]
+  - [64]
+  - [65]
+  - [66]
+  - [67]
+  - [68]
+  - [69]
+  - [70]
+  - [71]
+  - [72]
+  - [73]
+  - [74]
+  - [75]
+  - [76]
+  - [77]
+  - [78]
+  - [79]
+  - [80]
+  - [81]
+  - [82]
+  - [83]
+  - [84]
+  - [85]
+  - [86]
+  - [87]
+  - [88]
+  - [89]
+  - [90]
+  - [91]
+  - [92]
+  - [93]
+  - [94]
+  - [95]
+  - [96]
+  - [97]
+  - [98]
+  - [99]
+  - [100]
+...
+space:insert({7})
+---
+- error: Duplicate key exists in unique index 'primary' in space 'test'
+...
+space:drop()
+---
+...
+-- insert multi-part (num, num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num', 2, 'num'} })
+---
+...
+for key = 1, 100 do space:insert({key, key}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({key, key})) end
+---
+...
+t
+---
+- - [1, 1]
+  - [2, 2]
+  - [3, 3]
+  - [4, 4]
+  - [5, 5]
+  - [6, 6]
+  - [7, 7]
+  - [8, 8]
+  - [9, 9]
+  - [10, 10]
+  - [11, 11]
+  - [12, 12]
+  - [13, 13]
+  - [14, 14]
+  - [15, 15]
+  - [16, 16]
+  - [17, 17]
+  - [18, 18]
+  - [19, 19]
+  - [20, 20]
+  - [21, 21]
+  - [22, 22]
+  - [23, 23]
+  - [24, 24]
+  - [25, 25]
+  - [26, 26]
+  - [27, 27]
+  - [28, 28]
+  - [29, 29]
+  - [30, 30]
+  - [31, 31]
+  - [32, 32]
+  - [33, 33]
+  - [34, 34]
+  - [35, 35]
+  - [36, 36]
+  - [37, 37]
+  - [38, 38]
+  - [39, 39]
+  - [40, 40]
+  - [41, 41]
+  - [42, 42]
+  - [43, 43]
+  - [44, 44]
+  - [45, 45]
+  - [46, 46]
+  - [47, 47]
+  - [48, 48]
+  - [49, 49]
+  - [50, 50]
+  - [51, 51]
+  - [52, 52]
+  - [53, 53]
+  - [54, 54]
+  - [55, 55]
+  - [56, 56]
+  - [57, 57]
+  - [58, 58]
+  - [59, 59]
+  - [60, 60]
+  - [61, 61]
+  - [62, 62]
+  - [63, 63]
+  - [64, 64]
+  - [65, 65]
+  - [66, 66]
+  - [67, 67]
+  - [68, 68]
+  - [69, 69]
+  - [70, 70]
+  - [71, 71]
+  - [72, 72]
+  - [73, 73]
+  - [74, 74]
+  - [75, 75]
+  - [76, 76]
+  - [77, 77]
+  - [78, 78]
+  - [79, 79]
+  - [80, 80]
+  - [81, 81]
+  - [82, 82]
+  - [83, 83]
+  - [84, 84]
+  - [85, 85]
+  - [86, 86]
+  - [87, 87]
+  - [88, 88]
+  - [89, 89]
+  - [90, 90]
+  - [91, 91]
+  - [92, 92]
+  - [93, 93]
+  - [94, 94]
+  - [95, 95]
+  - [96, 96]
+  - [97, 97]
+  - [98, 98]
+  - [99, 99]
+  - [100, 100]
+...
+space:insert({7, 7})
+---
+- error: Duplicate key exists in unique index 'primary' in space 'test'
+...
+space:drop()
+---
+...
diff --git a/test/sophia/insert.test.lua b/test/sophia/insert.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..2153649d8df935f70b8764b1c80fb4801c6da098
--- /dev/null
+++ b/test/sophia/insert.test.lua
@@ -0,0 +1,32 @@
+
+-- insert (str)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'str'} })
+for key = 1, 100 do space:insert({tostring(key)}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({tostring(key)})) end
+t
+space:insert({tostring(7)})
+space:drop()
+
+
+-- insert (num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
+for key = 1, 100 do space:insert({key}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({key})) end
+t
+space:insert({7})
+space:drop()
+
+
+-- insert multi-part (num, num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num', 2, 'num'} })
+for key = 1, 100 do space:insert({key, key}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({key, key})) end
+t
+space:insert({7, 7})
+space:drop()
diff --git a/test/sophia/iterator.result b/test/sophia/iterator.result
new file mode 100644
index 0000000000000000000000000000000000000000..bcda4f77af9727416c662eccb2d61ae05ac0e439
--- /dev/null
+++ b/test/sophia/iterator.result
@@ -0,0 +1,2187 @@
+-- iterator (str)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'str'} })
+---
+...
+for key = 1, 100 do space:replace({tostring(key)}) end
+---
+...
+t = {} for state, v in index:pairs({}, {iterator = 'ALL'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['1']
+  - ['10']
+  - ['100']
+  - ['11']
+  - ['12']
+  - ['13']
+  - ['14']
+  - ['15']
+  - ['16']
+  - ['17']
+  - ['18']
+  - ['19']
+  - ['2']
+  - ['20']
+  - ['21']
+  - ['22']
+  - ['23']
+  - ['24']
+  - ['25']
+  - ['26']
+  - ['27']
+  - ['28']
+  - ['29']
+  - ['3']
+  - ['30']
+  - ['31']
+  - ['32']
+  - ['33']
+  - ['34']
+  - ['35']
+  - ['36']
+  - ['37']
+  - ['38']
+  - ['39']
+  - ['4']
+  - ['40']
+  - ['41']
+  - ['42']
+  - ['43']
+  - ['44']
+  - ['45']
+  - ['46']
+  - ['47']
+  - ['48']
+  - ['49']
+  - ['5']
+  - ['50']
+  - ['51']
+  - ['52']
+  - ['53']
+  - ['54']
+  - ['55']
+  - ['56']
+  - ['57']
+  - ['58']
+  - ['59']
+  - ['6']
+  - ['60']
+  - ['61']
+  - ['62']
+  - ['63']
+  - ['64']
+  - ['65']
+  - ['66']
+  - ['67']
+  - ['68']
+  - ['69']
+  - ['7']
+  - ['70']
+  - ['71']
+  - ['72']
+  - ['73']
+  - ['74']
+  - ['75']
+  - ['76']
+  - ['77']
+  - ['78']
+  - ['79']
+  - ['8']
+  - ['80']
+  - ['81']
+  - ['82']
+  - ['83']
+  - ['84']
+  - ['85']
+  - ['86']
+  - ['87']
+  - ['88']
+  - ['89']
+  - ['9']
+  - ['90']
+  - ['91']
+  - ['92']
+  - ['93']
+  - ['94']
+  - ['95']
+  - ['96']
+  - ['97']
+  - ['98']
+  - ['99']
+...
+t = {} for state, v in index:pairs({}, {iterator = 'GE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['1']
+  - ['10']
+  - ['100']
+  - ['11']
+  - ['12']
+  - ['13']
+  - ['14']
+  - ['15']
+  - ['16']
+  - ['17']
+  - ['18']
+  - ['19']
+  - ['2']
+  - ['20']
+  - ['21']
+  - ['22']
+  - ['23']
+  - ['24']
+  - ['25']
+  - ['26']
+  - ['27']
+  - ['28']
+  - ['29']
+  - ['3']
+  - ['30']
+  - ['31']
+  - ['32']
+  - ['33']
+  - ['34']
+  - ['35']
+  - ['36']
+  - ['37']
+  - ['38']
+  - ['39']
+  - ['4']
+  - ['40']
+  - ['41']
+  - ['42']
+  - ['43']
+  - ['44']
+  - ['45']
+  - ['46']
+  - ['47']
+  - ['48']
+  - ['49']
+  - ['5']
+  - ['50']
+  - ['51']
+  - ['52']
+  - ['53']
+  - ['54']
+  - ['55']
+  - ['56']
+  - ['57']
+  - ['58']
+  - ['59']
+  - ['6']
+  - ['60']
+  - ['61']
+  - ['62']
+  - ['63']
+  - ['64']
+  - ['65']
+  - ['66']
+  - ['67']
+  - ['68']
+  - ['69']
+  - ['7']
+  - ['70']
+  - ['71']
+  - ['72']
+  - ['73']
+  - ['74']
+  - ['75']
+  - ['76']
+  - ['77']
+  - ['78']
+  - ['79']
+  - ['8']
+  - ['80']
+  - ['81']
+  - ['82']
+  - ['83']
+  - ['84']
+  - ['85']
+  - ['86']
+  - ['87']
+  - ['88']
+  - ['89']
+  - ['9']
+  - ['90']
+  - ['91']
+  - ['92']
+  - ['93']
+  - ['94']
+  - ['95']
+  - ['96']
+  - ['97']
+  - ['98']
+  - ['99']
+...
+t = {} for state, v in index:pairs(tostring(44), {iterator = 'GE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['44']
+  - ['45']
+  - ['46']
+  - ['47']
+  - ['48']
+  - ['49']
+  - ['5']
+  - ['50']
+  - ['51']
+  - ['52']
+  - ['53']
+  - ['54']
+  - ['55']
+  - ['56']
+  - ['57']
+  - ['58']
+  - ['59']
+  - ['6']
+  - ['60']
+  - ['61']
+  - ['62']
+  - ['63']
+  - ['64']
+  - ['65']
+  - ['66']
+  - ['67']
+  - ['68']
+  - ['69']
+  - ['7']
+  - ['70']
+  - ['71']
+  - ['72']
+  - ['73']
+  - ['74']
+  - ['75']
+  - ['76']
+  - ['77']
+  - ['78']
+  - ['79']
+  - ['8']
+  - ['80']
+  - ['81']
+  - ['82']
+  - ['83']
+  - ['84']
+  - ['85']
+  - ['86']
+  - ['87']
+  - ['88']
+  - ['89']
+  - ['9']
+  - ['90']
+  - ['91']
+  - ['92']
+  - ['93']
+  - ['94']
+  - ['95']
+  - ['96']
+  - ['97']
+  - ['98']
+  - ['99']
+...
+t = {} for state, v in index:pairs(tostring(44), {iterator = 'GT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['45']
+  - ['46']
+  - ['47']
+  - ['48']
+  - ['49']
+  - ['5']
+  - ['50']
+  - ['51']
+  - ['52']
+  - ['53']
+  - ['54']
+  - ['55']
+  - ['56']
+  - ['57']
+  - ['58']
+  - ['59']
+  - ['6']
+  - ['60']
+  - ['61']
+  - ['62']
+  - ['63']
+  - ['64']
+  - ['65']
+  - ['66']
+  - ['67']
+  - ['68']
+  - ['69']
+  - ['7']
+  - ['70']
+  - ['71']
+  - ['72']
+  - ['73']
+  - ['74']
+  - ['75']
+  - ['76']
+  - ['77']
+  - ['78']
+  - ['79']
+  - ['8']
+  - ['80']
+  - ['81']
+  - ['82']
+  - ['83']
+  - ['84']
+  - ['85']
+  - ['86']
+  - ['87']
+  - ['88']
+  - ['89']
+  - ['9']
+  - ['90']
+  - ['91']
+  - ['92']
+  - ['93']
+  - ['94']
+  - ['95']
+  - ['96']
+  - ['97']
+  - ['98']
+  - ['99']
+...
+t = {} for state, v in index:pairs({}, {iterator = 'LE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['99']
+  - ['98']
+  - ['97']
+  - ['96']
+  - ['95']
+  - ['94']
+  - ['93']
+  - ['92']
+  - ['91']
+  - ['90']
+  - ['9']
+  - ['89']
+  - ['88']
+  - ['87']
+  - ['86']
+  - ['85']
+  - ['84']
+  - ['83']
+  - ['82']
+  - ['81']
+  - ['80']
+  - ['8']
+  - ['79']
+  - ['78']
+  - ['77']
+  - ['76']
+  - ['75']
+  - ['74']
+  - ['73']
+  - ['72']
+  - ['71']
+  - ['70']
+  - ['7']
+  - ['69']
+  - ['68']
+  - ['67']
+  - ['66']
+  - ['65']
+  - ['64']
+  - ['63']
+  - ['62']
+  - ['61']
+  - ['60']
+  - ['6']
+  - ['59']
+  - ['58']
+  - ['57']
+  - ['56']
+  - ['55']
+  - ['54']
+  - ['53']
+  - ['52']
+  - ['51']
+  - ['50']
+  - ['5']
+  - ['49']
+  - ['48']
+  - ['47']
+  - ['46']
+  - ['45']
+  - ['44']
+  - ['43']
+  - ['42']
+  - ['41']
+  - ['40']
+  - ['4']
+  - ['39']
+  - ['38']
+  - ['37']
+  - ['36']
+  - ['35']
+  - ['34']
+  - ['33']
+  - ['32']
+  - ['31']
+  - ['30']
+  - ['3']
+  - ['29']
+  - ['28']
+  - ['27']
+  - ['26']
+  - ['25']
+  - ['24']
+  - ['23']
+  - ['22']
+  - ['21']
+  - ['20']
+  - ['2']
+  - ['19']
+  - ['18']
+  - ['17']
+  - ['16']
+  - ['15']
+  - ['14']
+  - ['13']
+  - ['12']
+  - ['11']
+  - ['100']
+  - ['10']
+  - ['1']
+...
+t = {} for state, v in index:pairs(tostring(77), {iterator = 'LE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['77']
+  - ['76']
+  - ['75']
+  - ['74']
+  - ['73']
+  - ['72']
+  - ['71']
+  - ['70']
+  - ['7']
+  - ['69']
+  - ['68']
+  - ['67']
+  - ['66']
+  - ['65']
+  - ['64']
+  - ['63']
+  - ['62']
+  - ['61']
+  - ['60']
+  - ['6']
+  - ['59']
+  - ['58']
+  - ['57']
+  - ['56']
+  - ['55']
+  - ['54']
+  - ['53']
+  - ['52']
+  - ['51']
+  - ['50']
+  - ['5']
+  - ['49']
+  - ['48']
+  - ['47']
+  - ['46']
+  - ['45']
+  - ['44']
+  - ['43']
+  - ['42']
+  - ['41']
+  - ['40']
+  - ['4']
+  - ['39']
+  - ['38']
+  - ['37']
+  - ['36']
+  - ['35']
+  - ['34']
+  - ['33']
+  - ['32']
+  - ['31']
+  - ['30']
+  - ['3']
+  - ['29']
+  - ['28']
+  - ['27']
+  - ['26']
+  - ['25']
+  - ['24']
+  - ['23']
+  - ['22']
+  - ['21']
+  - ['20']
+  - ['2']
+  - ['19']
+  - ['18']
+  - ['17']
+  - ['16']
+  - ['15']
+  - ['14']
+  - ['13']
+  - ['12']
+  - ['11']
+  - ['100']
+  - ['10']
+  - ['1']
+...
+t = {} for state, v in index:pairs({}, {iterator = 'LT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['99']
+  - ['98']
+  - ['97']
+  - ['96']
+  - ['95']
+  - ['94']
+  - ['93']
+  - ['92']
+  - ['91']
+  - ['90']
+  - ['9']
+  - ['89']
+  - ['88']
+  - ['87']
+  - ['86']
+  - ['85']
+  - ['84']
+  - ['83']
+  - ['82']
+  - ['81']
+  - ['80']
+  - ['8']
+  - ['79']
+  - ['78']
+  - ['77']
+  - ['76']
+  - ['75']
+  - ['74']
+  - ['73']
+  - ['72']
+  - ['71']
+  - ['70']
+  - ['7']
+  - ['69']
+  - ['68']
+  - ['67']
+  - ['66']
+  - ['65']
+  - ['64']
+  - ['63']
+  - ['62']
+  - ['61']
+  - ['60']
+  - ['6']
+  - ['59']
+  - ['58']
+  - ['57']
+  - ['56']
+  - ['55']
+  - ['54']
+  - ['53']
+  - ['52']
+  - ['51']
+  - ['50']
+  - ['5']
+  - ['49']
+  - ['48']
+  - ['47']
+  - ['46']
+  - ['45']
+  - ['44']
+  - ['43']
+  - ['42']
+  - ['41']
+  - ['40']
+  - ['4']
+  - ['39']
+  - ['38']
+  - ['37']
+  - ['36']
+  - ['35']
+  - ['34']
+  - ['33']
+  - ['32']
+  - ['31']
+  - ['30']
+  - ['3']
+  - ['29']
+  - ['28']
+  - ['27']
+  - ['26']
+  - ['25']
+  - ['24']
+  - ['23']
+  - ['22']
+  - ['21']
+  - ['20']
+  - ['2']
+  - ['19']
+  - ['18']
+  - ['17']
+  - ['16']
+  - ['15']
+  - ['14']
+  - ['13']
+  - ['12']
+  - ['11']
+  - ['100']
+  - ['10']
+  - ['1']
+...
+t = {} for state, v in index:pairs(tostring(77), {iterator = 'LT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['76']
+  - ['75']
+  - ['74']
+  - ['73']
+  - ['72']
+  - ['71']
+  - ['70']
+  - ['7']
+  - ['69']
+  - ['68']
+  - ['67']
+  - ['66']
+  - ['65']
+  - ['64']
+  - ['63']
+  - ['62']
+  - ['61']
+  - ['60']
+  - ['6']
+  - ['59']
+  - ['58']
+  - ['57']
+  - ['56']
+  - ['55']
+  - ['54']
+  - ['53']
+  - ['52']
+  - ['51']
+  - ['50']
+  - ['5']
+  - ['49']
+  - ['48']
+  - ['47']
+  - ['46']
+  - ['45']
+  - ['44']
+  - ['43']
+  - ['42']
+  - ['41']
+  - ['40']
+  - ['4']
+  - ['39']
+  - ['38']
+  - ['37']
+  - ['36']
+  - ['35']
+  - ['34']
+  - ['33']
+  - ['32']
+  - ['31']
+  - ['30']
+  - ['3']
+  - ['29']
+  - ['28']
+  - ['27']
+  - ['26']
+  - ['25']
+  - ['24']
+  - ['23']
+  - ['22']
+  - ['21']
+  - ['20']
+  - ['2']
+  - ['19']
+  - ['18']
+  - ['17']
+  - ['16']
+  - ['15']
+  - ['14']
+  - ['13']
+  - ['12']
+  - ['11']
+  - ['100']
+  - ['10']
+  - ['1']
+...
+space:drop()
+---
+...
+-- iterator (num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
+---
+...
+for key = 1, 100 do space:replace({key}) end
+---
+...
+t = {} for state, v in index:pairs({}, {iterator = 'ALL'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [1]
+  - [2]
+  - [3]
+  - [4]
+  - [5]
+  - [6]
+  - [7]
+  - [8]
+  - [9]
+  - [10]
+  - [11]
+  - [12]
+  - [13]
+  - [14]
+  - [15]
+  - [16]
+  - [17]
+  - [18]
+  - [19]
+  - [20]
+  - [21]
+  - [22]
+  - [23]
+  - [24]
+  - [25]
+  - [26]
+  - [27]
+  - [28]
+  - [29]
+  - [30]
+  - [31]
+  - [32]
+  - [33]
+  - [34]
+  - [35]
+  - [36]
+  - [37]
+  - [38]
+  - [39]
+  - [40]
+  - [41]
+  - [42]
+  - [43]
+  - [44]
+  - [45]
+  - [46]
+  - [47]
+  - [48]
+  - [49]
+  - [50]
+  - [51]
+  - [52]
+  - [53]
+  - [54]
+  - [55]
+  - [56]
+  - [57]
+  - [58]
+  - [59]
+  - [60]
+  - [61]
+  - [62]
+  - [63]
+  - [64]
+  - [65]
+  - [66]
+  - [67]
+  - [68]
+  - [69]
+  - [70]
+  - [71]
+  - [72]
+  - [73]
+  - [74]
+  - [75]
+  - [76]
+  - [77]
+  - [78]
+  - [79]
+  - [80]
+  - [81]
+  - [82]
+  - [83]
+  - [84]
+  - [85]
+  - [86]
+  - [87]
+  - [88]
+  - [89]
+  - [90]
+  - [91]
+  - [92]
+  - [93]
+  - [94]
+  - [95]
+  - [96]
+  - [97]
+  - [98]
+  - [99]
+  - [100]
+...
+t = {} for state, v in index:pairs({}, {iterator = 'GE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [1]
+  - [2]
+  - [3]
+  - [4]
+  - [5]
+  - [6]
+  - [7]
+  - [8]
+  - [9]
+  - [10]
+  - [11]
+  - [12]
+  - [13]
+  - [14]
+  - [15]
+  - [16]
+  - [17]
+  - [18]
+  - [19]
+  - [20]
+  - [21]
+  - [22]
+  - [23]
+  - [24]
+  - [25]
+  - [26]
+  - [27]
+  - [28]
+  - [29]
+  - [30]
+  - [31]
+  - [32]
+  - [33]
+  - [34]
+  - [35]
+  - [36]
+  - [37]
+  - [38]
+  - [39]
+  - [40]
+  - [41]
+  - [42]
+  - [43]
+  - [44]
+  - [45]
+  - [46]
+  - [47]
+  - [48]
+  - [49]
+  - [50]
+  - [51]
+  - [52]
+  - [53]
+  - [54]
+  - [55]
+  - [56]
+  - [57]
+  - [58]
+  - [59]
+  - [60]
+  - [61]
+  - [62]
+  - [63]
+  - [64]
+  - [65]
+  - [66]
+  - [67]
+  - [68]
+  - [69]
+  - [70]
+  - [71]
+  - [72]
+  - [73]
+  - [74]
+  - [75]
+  - [76]
+  - [77]
+  - [78]
+  - [79]
+  - [80]
+  - [81]
+  - [82]
+  - [83]
+  - [84]
+  - [85]
+  - [86]
+  - [87]
+  - [88]
+  - [89]
+  - [90]
+  - [91]
+  - [92]
+  - [93]
+  - [94]
+  - [95]
+  - [96]
+  - [97]
+  - [98]
+  - [99]
+  - [100]
+...
+t = {} for state, v in index:pairs(44, {iterator = 'GE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [44]
+  - [45]
+  - [46]
+  - [47]
+  - [48]
+  - [49]
+  - [50]
+  - [51]
+  - [52]
+  - [53]
+  - [54]
+  - [55]
+  - [56]
+  - [57]
+  - [58]
+  - [59]
+  - [60]
+  - [61]
+  - [62]
+  - [63]
+  - [64]
+  - [65]
+  - [66]
+  - [67]
+  - [68]
+  - [69]
+  - [70]
+  - [71]
+  - [72]
+  - [73]
+  - [74]
+  - [75]
+  - [76]
+  - [77]
+  - [78]
+  - [79]
+  - [80]
+  - [81]
+  - [82]
+  - [83]
+  - [84]
+  - [85]
+  - [86]
+  - [87]
+  - [88]
+  - [89]
+  - [90]
+  - [91]
+  - [92]
+  - [93]
+  - [94]
+  - [95]
+  - [96]
+  - [97]
+  - [98]
+  - [99]
+  - [100]
+...
+t = {} for state, v in index:pairs(44, {iterator = 'GT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [45]
+  - [46]
+  - [47]
+  - [48]
+  - [49]
+  - [50]
+  - [51]
+  - [52]
+  - [53]
+  - [54]
+  - [55]
+  - [56]
+  - [57]
+  - [58]
+  - [59]
+  - [60]
+  - [61]
+  - [62]
+  - [63]
+  - [64]
+  - [65]
+  - [66]
+  - [67]
+  - [68]
+  - [69]
+  - [70]
+  - [71]
+  - [72]
+  - [73]
+  - [74]
+  - [75]
+  - [76]
+  - [77]
+  - [78]
+  - [79]
+  - [80]
+  - [81]
+  - [82]
+  - [83]
+  - [84]
+  - [85]
+  - [86]
+  - [87]
+  - [88]
+  - [89]
+  - [90]
+  - [91]
+  - [92]
+  - [93]
+  - [94]
+  - [95]
+  - [96]
+  - [97]
+  - [98]
+  - [99]
+  - [100]
+...
+t = {} for state, v in index:pairs({}, {iterator = 'LE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [100]
+  - [99]
+  - [98]
+  - [97]
+  - [96]
+  - [95]
+  - [94]
+  - [93]
+  - [92]
+  - [91]
+  - [90]
+  - [89]
+  - [88]
+  - [87]
+  - [86]
+  - [85]
+  - [84]
+  - [83]
+  - [82]
+  - [81]
+  - [80]
+  - [79]
+  - [78]
+  - [77]
+  - [76]
+  - [75]
+  - [74]
+  - [73]
+  - [72]
+  - [71]
+  - [70]
+  - [69]
+  - [68]
+  - [67]
+  - [66]
+  - [65]
+  - [64]
+  - [63]
+  - [62]
+  - [61]
+  - [60]
+  - [59]
+  - [58]
+  - [57]
+  - [56]
+  - [55]
+  - [54]
+  - [53]
+  - [52]
+  - [51]
+  - [50]
+  - [49]
+  - [48]
+  - [47]
+  - [46]
+  - [45]
+  - [44]
+  - [43]
+  - [42]
+  - [41]
+  - [40]
+  - [39]
+  - [38]
+  - [37]
+  - [36]
+  - [35]
+  - [34]
+  - [33]
+  - [32]
+  - [31]
+  - [30]
+  - [29]
+  - [28]
+  - [27]
+  - [26]
+  - [25]
+  - [24]
+  - [23]
+  - [22]
+  - [21]
+  - [20]
+  - [19]
+  - [18]
+  - [17]
+  - [16]
+  - [15]
+  - [14]
+  - [13]
+  - [12]
+  - [11]
+  - [10]
+  - [9]
+  - [8]
+  - [7]
+  - [6]
+  - [5]
+  - [4]
+  - [3]
+  - [2]
+  - [1]
+...
+t = {} for state, v in index:pairs(77, {iterator = 'LE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [77]
+  - [76]
+  - [75]
+  - [74]
+  - [73]
+  - [72]
+  - [71]
+  - [70]
+  - [69]
+  - [68]
+  - [67]
+  - [66]
+  - [65]
+  - [64]
+  - [63]
+  - [62]
+  - [61]
+  - [60]
+  - [59]
+  - [58]
+  - [57]
+  - [56]
+  - [55]
+  - [54]
+  - [53]
+  - [52]
+  - [51]
+  - [50]
+  - [49]
+  - [48]
+  - [47]
+  - [46]
+  - [45]
+  - [44]
+  - [43]
+  - [42]
+  - [41]
+  - [40]
+  - [39]
+  - [38]
+  - [37]
+  - [36]
+  - [35]
+  - [34]
+  - [33]
+  - [32]
+  - [31]
+  - [30]
+  - [29]
+  - [28]
+  - [27]
+  - [26]
+  - [25]
+  - [24]
+  - [23]
+  - [22]
+  - [21]
+  - [20]
+  - [19]
+  - [18]
+  - [17]
+  - [16]
+  - [15]
+  - [14]
+  - [13]
+  - [12]
+  - [11]
+  - [10]
+  - [9]
+  - [8]
+  - [7]
+  - [6]
+  - [5]
+  - [4]
+  - [3]
+  - [2]
+  - [1]
+...
+t = {} for state, v in index:pairs({}, {iterator = 'LT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [100]
+  - [99]
+  - [98]
+  - [97]
+  - [96]
+  - [95]
+  - [94]
+  - [93]
+  - [92]
+  - [91]
+  - [90]
+  - [89]
+  - [88]
+  - [87]
+  - [86]
+  - [85]
+  - [84]
+  - [83]
+  - [82]
+  - [81]
+  - [80]
+  - [79]
+  - [78]
+  - [77]
+  - [76]
+  - [75]
+  - [74]
+  - [73]
+  - [72]
+  - [71]
+  - [70]
+  - [69]
+  - [68]
+  - [67]
+  - [66]
+  - [65]
+  - [64]
+  - [63]
+  - [62]
+  - [61]
+  - [60]
+  - [59]
+  - [58]
+  - [57]
+  - [56]
+  - [55]
+  - [54]
+  - [53]
+  - [52]
+  - [51]
+  - [50]
+  - [49]
+  - [48]
+  - [47]
+  - [46]
+  - [45]
+  - [44]
+  - [43]
+  - [42]
+  - [41]
+  - [40]
+  - [39]
+  - [38]
+  - [37]
+  - [36]
+  - [35]
+  - [34]
+  - [33]
+  - [32]
+  - [31]
+  - [30]
+  - [29]
+  - [28]
+  - [27]
+  - [26]
+  - [25]
+  - [24]
+  - [23]
+  - [22]
+  - [21]
+  - [20]
+  - [19]
+  - [18]
+  - [17]
+  - [16]
+  - [15]
+  - [14]
+  - [13]
+  - [12]
+  - [11]
+  - [10]
+  - [9]
+  - [8]
+  - [7]
+  - [6]
+  - [5]
+  - [4]
+  - [3]
+  - [2]
+  - [1]
+...
+t = {} for state, v in index:pairs(77, {iterator = 'LT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [76]
+  - [75]
+  - [74]
+  - [73]
+  - [72]
+  - [71]
+  - [70]
+  - [69]
+  - [68]
+  - [67]
+  - [66]
+  - [65]
+  - [64]
+  - [63]
+  - [62]
+  - [61]
+  - [60]
+  - [59]
+  - [58]
+  - [57]
+  - [56]
+  - [55]
+  - [54]
+  - [53]
+  - [52]
+  - [51]
+  - [50]
+  - [49]
+  - [48]
+  - [47]
+  - [46]
+  - [45]
+  - [44]
+  - [43]
+  - [42]
+  - [41]
+  - [40]
+  - [39]
+  - [38]
+  - [37]
+  - [36]
+  - [35]
+  - [34]
+  - [33]
+  - [32]
+  - [31]
+  - [30]
+  - [29]
+  - [28]
+  - [27]
+  - [26]
+  - [25]
+  - [24]
+  - [23]
+  - [22]
+  - [21]
+  - [20]
+  - [19]
+  - [18]
+  - [17]
+  - [16]
+  - [15]
+  - [14]
+  - [13]
+  - [12]
+  - [11]
+  - [10]
+  - [9]
+  - [8]
+  - [7]
+  - [6]
+  - [5]
+  - [4]
+  - [3]
+  - [2]
+  - [1]
+...
+space:drop()
+---
+...
+-- iterator multi-part (num, num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num', 2, 'num'} })
+---
+...
+for key = 1, 100 do space:replace({key, key}) end
+---
+...
+t = {} for state, v in index:pairs({}, {iterator = 'ALL'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [1, 1]
+  - [2, 2]
+  - [3, 3]
+  - [4, 4]
+  - [5, 5]
+  - [6, 6]
+  - [7, 7]
+  - [8, 8]
+  - [9, 9]
+  - [10, 10]
+  - [11, 11]
+  - [12, 12]
+  - [13, 13]
+  - [14, 14]
+  - [15, 15]
+  - [16, 16]
+  - [17, 17]
+  - [18, 18]
+  - [19, 19]
+  - [20, 20]
+  - [21, 21]
+  - [22, 22]
+  - [23, 23]
+  - [24, 24]
+  - [25, 25]
+  - [26, 26]
+  - [27, 27]
+  - [28, 28]
+  - [29, 29]
+  - [30, 30]
+  - [31, 31]
+  - [32, 32]
+  - [33, 33]
+  - [34, 34]
+  - [35, 35]
+  - [36, 36]
+  - [37, 37]
+  - [38, 38]
+  - [39, 39]
+  - [40, 40]
+  - [41, 41]
+  - [42, 42]
+  - [43, 43]
+  - [44, 44]
+  - [45, 45]
+  - [46, 46]
+  - [47, 47]
+  - [48, 48]
+  - [49, 49]
+  - [50, 50]
+  - [51, 51]
+  - [52, 52]
+  - [53, 53]
+  - [54, 54]
+  - [55, 55]
+  - [56, 56]
+  - [57, 57]
+  - [58, 58]
+  - [59, 59]
+  - [60, 60]
+  - [61, 61]
+  - [62, 62]
+  - [63, 63]
+  - [64, 64]
+  - [65, 65]
+  - [66, 66]
+  - [67, 67]
+  - [68, 68]
+  - [69, 69]
+  - [70, 70]
+  - [71, 71]
+  - [72, 72]
+  - [73, 73]
+  - [74, 74]
+  - [75, 75]
+  - [76, 76]
+  - [77, 77]
+  - [78, 78]
+  - [79, 79]
+  - [80, 80]
+  - [81, 81]
+  - [82, 82]
+  - [83, 83]
+  - [84, 84]
+  - [85, 85]
+  - [86, 86]
+  - [87, 87]
+  - [88, 88]
+  - [89, 89]
+  - [90, 90]
+  - [91, 91]
+  - [92, 92]
+  - [93, 93]
+  - [94, 94]
+  - [95, 95]
+  - [96, 96]
+  - [97, 97]
+  - [98, 98]
+  - [99, 99]
+  - [100, 100]
+...
+t = {} for state, v in index:pairs({}, {iterator = 'GE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [1, 1]
+  - [2, 2]
+  - [3, 3]
+  - [4, 4]
+  - [5, 5]
+  - [6, 6]
+  - [7, 7]
+  - [8, 8]
+  - [9, 9]
+  - [10, 10]
+  - [11, 11]
+  - [12, 12]
+  - [13, 13]
+  - [14, 14]
+  - [15, 15]
+  - [16, 16]
+  - [17, 17]
+  - [18, 18]
+  - [19, 19]
+  - [20, 20]
+  - [21, 21]
+  - [22, 22]
+  - [23, 23]
+  - [24, 24]
+  - [25, 25]
+  - [26, 26]
+  - [27, 27]
+  - [28, 28]
+  - [29, 29]
+  - [30, 30]
+  - [31, 31]
+  - [32, 32]
+  - [33, 33]
+  - [34, 34]
+  - [35, 35]
+  - [36, 36]
+  - [37, 37]
+  - [38, 38]
+  - [39, 39]
+  - [40, 40]
+  - [41, 41]
+  - [42, 42]
+  - [43, 43]
+  - [44, 44]
+  - [45, 45]
+  - [46, 46]
+  - [47, 47]
+  - [48, 48]
+  - [49, 49]
+  - [50, 50]
+  - [51, 51]
+  - [52, 52]
+  - [53, 53]
+  - [54, 54]
+  - [55, 55]
+  - [56, 56]
+  - [57, 57]
+  - [58, 58]
+  - [59, 59]
+  - [60, 60]
+  - [61, 61]
+  - [62, 62]
+  - [63, 63]
+  - [64, 64]
+  - [65, 65]
+  - [66, 66]
+  - [67, 67]
+  - [68, 68]
+  - [69, 69]
+  - [70, 70]
+  - [71, 71]
+  - [72, 72]
+  - [73, 73]
+  - [74, 74]
+  - [75, 75]
+  - [76, 76]
+  - [77, 77]
+  - [78, 78]
+  - [79, 79]
+  - [80, 80]
+  - [81, 81]
+  - [82, 82]
+  - [83, 83]
+  - [84, 84]
+  - [85, 85]
+  - [86, 86]
+  - [87, 87]
+  - [88, 88]
+  - [89, 89]
+  - [90, 90]
+  - [91, 91]
+  - [92, 92]
+  - [93, 93]
+  - [94, 94]
+  - [95, 95]
+  - [96, 96]
+  - [97, 97]
+  - [98, 98]
+  - [99, 99]
+  - [100, 100]
+...
+t = {} for state, v in index:pairs({44, 44}, {iterator = 'GE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [44, 44]
+  - [45, 45]
+  - [46, 46]
+  - [47, 47]
+  - [48, 48]
+  - [49, 49]
+  - [50, 50]
+  - [51, 51]
+  - [52, 52]
+  - [53, 53]
+  - [54, 54]
+  - [55, 55]
+  - [56, 56]
+  - [57, 57]
+  - [58, 58]
+  - [59, 59]
+  - [60, 60]
+  - [61, 61]
+  - [62, 62]
+  - [63, 63]
+  - [64, 64]
+  - [65, 65]
+  - [66, 66]
+  - [67, 67]
+  - [68, 68]
+  - [69, 69]
+  - [70, 70]
+  - [71, 71]
+  - [72, 72]
+  - [73, 73]
+  - [74, 74]
+  - [75, 75]
+  - [76, 76]
+  - [77, 77]
+  - [78, 78]
+  - [79, 79]
+  - [80, 80]
+  - [81, 81]
+  - [82, 82]
+  - [83, 83]
+  - [84, 84]
+  - [85, 85]
+  - [86, 86]
+  - [87, 87]
+  - [88, 88]
+  - [89, 89]
+  - [90, 90]
+  - [91, 91]
+  - [92, 92]
+  - [93, 93]
+  - [94, 94]
+  - [95, 95]
+  - [96, 96]
+  - [97, 97]
+  - [98, 98]
+  - [99, 99]
+  - [100, 100]
+...
+t = {} for state, v in index:pairs({44, 44}, {iterator = 'GT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [45, 45]
+  - [46, 46]
+  - [47, 47]
+  - [48, 48]
+  - [49, 49]
+  - [50, 50]
+  - [51, 51]
+  - [52, 52]
+  - [53, 53]
+  - [54, 54]
+  - [55, 55]
+  - [56, 56]
+  - [57, 57]
+  - [58, 58]
+  - [59, 59]
+  - [60, 60]
+  - [61, 61]
+  - [62, 62]
+  - [63, 63]
+  - [64, 64]
+  - [65, 65]
+  - [66, 66]
+  - [67, 67]
+  - [68, 68]
+  - [69, 69]
+  - [70, 70]
+  - [71, 71]
+  - [72, 72]
+  - [73, 73]
+  - [74, 74]
+  - [75, 75]
+  - [76, 76]
+  - [77, 77]
+  - [78, 78]
+  - [79, 79]
+  - [80, 80]
+  - [81, 81]
+  - [82, 82]
+  - [83, 83]
+  - [84, 84]
+  - [85, 85]
+  - [86, 86]
+  - [87, 87]
+  - [88, 88]
+  - [89, 89]
+  - [90, 90]
+  - [91, 91]
+  - [92, 92]
+  - [93, 93]
+  - [94, 94]
+  - [95, 95]
+  - [96, 96]
+  - [97, 97]
+  - [98, 98]
+  - [99, 99]
+  - [100, 100]
+...
+t = {} for state, v in index:pairs({}, {iterator = 'LE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [100, 100]
+  - [99, 99]
+  - [98, 98]
+  - [97, 97]
+  - [96, 96]
+  - [95, 95]
+  - [94, 94]
+  - [93, 93]
+  - [92, 92]
+  - [91, 91]
+  - [90, 90]
+  - [89, 89]
+  - [88, 88]
+  - [87, 87]
+  - [86, 86]
+  - [85, 85]
+  - [84, 84]
+  - [83, 83]
+  - [82, 82]
+  - [81, 81]
+  - [80, 80]
+  - [79, 79]
+  - [78, 78]
+  - [77, 77]
+  - [76, 76]
+  - [75, 75]
+  - [74, 74]
+  - [73, 73]
+  - [72, 72]
+  - [71, 71]
+  - [70, 70]
+  - [69, 69]
+  - [68, 68]
+  - [67, 67]
+  - [66, 66]
+  - [65, 65]
+  - [64, 64]
+  - [63, 63]
+  - [62, 62]
+  - [61, 61]
+  - [60, 60]
+  - [59, 59]
+  - [58, 58]
+  - [57, 57]
+  - [56, 56]
+  - [55, 55]
+  - [54, 54]
+  - [53, 53]
+  - [52, 52]
+  - [51, 51]
+  - [50, 50]
+  - [49, 49]
+  - [48, 48]
+  - [47, 47]
+  - [46, 46]
+  - [45, 45]
+  - [44, 44]
+  - [43, 43]
+  - [42, 42]
+  - [41, 41]
+  - [40, 40]
+  - [39, 39]
+  - [38, 38]
+  - [37, 37]
+  - [36, 36]
+  - [35, 35]
+  - [34, 34]
+  - [33, 33]
+  - [32, 32]
+  - [31, 31]
+  - [30, 30]
+  - [29, 29]
+  - [28, 28]
+  - [27, 27]
+  - [26, 26]
+  - [25, 25]
+  - [24, 24]
+  - [23, 23]
+  - [22, 22]
+  - [21, 21]
+  - [20, 20]
+  - [19, 19]
+  - [18, 18]
+  - [17, 17]
+  - [16, 16]
+  - [15, 15]
+  - [14, 14]
+  - [13, 13]
+  - [12, 12]
+  - [11, 11]
+  - [10, 10]
+  - [9, 9]
+  - [8, 8]
+  - [7, 7]
+  - [6, 6]
+  - [5, 5]
+  - [4, 4]
+  - [3, 3]
+  - [2, 2]
+  - [1, 1]
+...
+t = {} for state, v in index:pairs({77, 77}, {iterator = 'LE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [77, 77]
+  - [76, 76]
+  - [75, 75]
+  - [74, 74]
+  - [73, 73]
+  - [72, 72]
+  - [71, 71]
+  - [70, 70]
+  - [69, 69]
+  - [68, 68]
+  - [67, 67]
+  - [66, 66]
+  - [65, 65]
+  - [64, 64]
+  - [63, 63]
+  - [62, 62]
+  - [61, 61]
+  - [60, 60]
+  - [59, 59]
+  - [58, 58]
+  - [57, 57]
+  - [56, 56]
+  - [55, 55]
+  - [54, 54]
+  - [53, 53]
+  - [52, 52]
+  - [51, 51]
+  - [50, 50]
+  - [49, 49]
+  - [48, 48]
+  - [47, 47]
+  - [46, 46]
+  - [45, 45]
+  - [44, 44]
+  - [43, 43]
+  - [42, 42]
+  - [41, 41]
+  - [40, 40]
+  - [39, 39]
+  - [38, 38]
+  - [37, 37]
+  - [36, 36]
+  - [35, 35]
+  - [34, 34]
+  - [33, 33]
+  - [32, 32]
+  - [31, 31]
+  - [30, 30]
+  - [29, 29]
+  - [28, 28]
+  - [27, 27]
+  - [26, 26]
+  - [25, 25]
+  - [24, 24]
+  - [23, 23]
+  - [22, 22]
+  - [21, 21]
+  - [20, 20]
+  - [19, 19]
+  - [18, 18]
+  - [17, 17]
+  - [16, 16]
+  - [15, 15]
+  - [14, 14]
+  - [13, 13]
+  - [12, 12]
+  - [11, 11]
+  - [10, 10]
+  - [9, 9]
+  - [8, 8]
+  - [7, 7]
+  - [6, 6]
+  - [5, 5]
+  - [4, 4]
+  - [3, 3]
+  - [2, 2]
+  - [1, 1]
+...
+t = {} for state, v in index:pairs({}, {iterator = 'LT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [100, 100]
+  - [99, 99]
+  - [98, 98]
+  - [97, 97]
+  - [96, 96]
+  - [95, 95]
+  - [94, 94]
+  - [93, 93]
+  - [92, 92]
+  - [91, 91]
+  - [90, 90]
+  - [89, 89]
+  - [88, 88]
+  - [87, 87]
+  - [86, 86]
+  - [85, 85]
+  - [84, 84]
+  - [83, 83]
+  - [82, 82]
+  - [81, 81]
+  - [80, 80]
+  - [79, 79]
+  - [78, 78]
+  - [77, 77]
+  - [76, 76]
+  - [75, 75]
+  - [74, 74]
+  - [73, 73]
+  - [72, 72]
+  - [71, 71]
+  - [70, 70]
+  - [69, 69]
+  - [68, 68]
+  - [67, 67]
+  - [66, 66]
+  - [65, 65]
+  - [64, 64]
+  - [63, 63]
+  - [62, 62]
+  - [61, 61]
+  - [60, 60]
+  - [59, 59]
+  - [58, 58]
+  - [57, 57]
+  - [56, 56]
+  - [55, 55]
+  - [54, 54]
+  - [53, 53]
+  - [52, 52]
+  - [51, 51]
+  - [50, 50]
+  - [49, 49]
+  - [48, 48]
+  - [47, 47]
+  - [46, 46]
+  - [45, 45]
+  - [44, 44]
+  - [43, 43]
+  - [42, 42]
+  - [41, 41]
+  - [40, 40]
+  - [39, 39]
+  - [38, 38]
+  - [37, 37]
+  - [36, 36]
+  - [35, 35]
+  - [34, 34]
+  - [33, 33]
+  - [32, 32]
+  - [31, 31]
+  - [30, 30]
+  - [29, 29]
+  - [28, 28]
+  - [27, 27]
+  - [26, 26]
+  - [25, 25]
+  - [24, 24]
+  - [23, 23]
+  - [22, 22]
+  - [21, 21]
+  - [20, 20]
+  - [19, 19]
+  - [18, 18]
+  - [17, 17]
+  - [16, 16]
+  - [15, 15]
+  - [14, 14]
+  - [13, 13]
+  - [12, 12]
+  - [11, 11]
+  - [10, 10]
+  - [9, 9]
+  - [8, 8]
+  - [7, 7]
+  - [6, 6]
+  - [5, 5]
+  - [4, 4]
+  - [3, 3]
+  - [2, 2]
+  - [1, 1]
+...
+t = {} for state, v in index:pairs({77, 77}, {iterator = 'LT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [76, 76]
+  - [75, 75]
+  - [74, 74]
+  - [73, 73]
+  - [72, 72]
+  - [71, 71]
+  - [70, 70]
+  - [69, 69]
+  - [68, 68]
+  - [67, 67]
+  - [66, 66]
+  - [65, 65]
+  - [64, 64]
+  - [63, 63]
+  - [62, 62]
+  - [61, 61]
+  - [60, 60]
+  - [59, 59]
+  - [58, 58]
+  - [57, 57]
+  - [56, 56]
+  - [55, 55]
+  - [54, 54]
+  - [53, 53]
+  - [52, 52]
+  - [51, 51]
+  - [50, 50]
+  - [49, 49]
+  - [48, 48]
+  - [47, 47]
+  - [46, 46]
+  - [45, 45]
+  - [44, 44]
+  - [43, 43]
+  - [42, 42]
+  - [41, 41]
+  - [40, 40]
+  - [39, 39]
+  - [38, 38]
+  - [37, 37]
+  - [36, 36]
+  - [35, 35]
+  - [34, 34]
+  - [33, 33]
+  - [32, 32]
+  - [31, 31]
+  - [30, 30]
+  - [29, 29]
+  - [28, 28]
+  - [27, 27]
+  - [26, 26]
+  - [25, 25]
+  - [24, 24]
+  - [23, 23]
+  - [22, 22]
+  - [21, 21]
+  - [20, 20]
+  - [19, 19]
+  - [18, 18]
+  - [17, 17]
+  - [16, 16]
+  - [15, 15]
+  - [14, 14]
+  - [13, 13]
+  - [12, 12]
+  - [11, 11]
+  - [10, 10]
+  - [9, 9]
+  - [8, 8]
+  - [7, 7]
+  - [6, 6]
+  - [5, 5]
+  - [4, 4]
+  - [3, 3]
+  - [2, 2]
+  - [1, 1]
+...
+space:drop()
+---
+...
diff --git a/test/sophia/iterator.test.lua b/test/sophia/iterator.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..72a1a35616d89ba269b02267032a834c48b3948d
--- /dev/null
+++ b/test/sophia/iterator.test.lua
@@ -0,0 +1,68 @@
+
+-- iterator (str)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'str'} })
+for key = 1, 100 do space:replace({tostring(key)}) end
+t = {} for state, v in index:pairs({}, {iterator = 'ALL'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs({}, {iterator = 'GE'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs(tostring(44), {iterator = 'GE'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs(tostring(44), {iterator = 'GT'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs({}, {iterator = 'LE'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs(tostring(77), {iterator = 'LE'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs({}, {iterator = 'LT'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs(tostring(77), {iterator = 'LT'}) do table.insert(t, v) end
+t
+space:drop()
+
+
+-- iterator (num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
+for key = 1, 100 do space:replace({key}) end
+t = {} for state, v in index:pairs({}, {iterator = 'ALL'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs({}, {iterator = 'GE'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs(44, {iterator = 'GE'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs(44, {iterator = 'GT'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs({}, {iterator = 'LE'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs(77, {iterator = 'LE'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs({}, {iterator = 'LT'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs(77, {iterator = 'LT'}) do table.insert(t, v) end
+t
+space:drop()
+
+
+-- iterator multi-part (num, num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num', 2, 'num'} })
+for key = 1, 100 do space:replace({key, key}) end
+t = {} for state, v in index:pairs({}, {iterator = 'ALL'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs({}, {iterator = 'GE'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs({44, 44}, {iterator = 'GE'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs({44, 44}, {iterator = 'GT'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs({}, {iterator = 'LE'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs({77, 77}, {iterator = 'LE'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs({}, {iterator = 'LT'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs({77, 77}, {iterator = 'LT'}) do table.insert(t, v) end
+t
+space:drop()
diff --git a/test/sophia/info.result b/test/sophia/monitoring.result
similarity index 55%
rename from test/sophia/info.result
rename to test/sophia/monitoring.result
index 2fe4e8e982c37a00cf2bf465db1dffc668f383a4..1d5bac8d48bc32aa167adbea0c1655bbef0ea757 100644
--- a/test/sophia/info.result
+++ b/test/sophia/monitoring.result
@@ -1,14 +1,13 @@
--- box.info().sophia['sophia.version']
 space = box.schema.space.create('test', { engine = 'sophia' })
 ---
 ...
-index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'str'} })
 ---
 ...
-for key = 1, 10 do space:insert({key}) end
+box.sophia()['sophia.version']
 ---
+- 2.1.1
 ...
--- box.info().sophia['db.100.index.count']
 space:drop()
 ---
 ...
diff --git a/test/sophia/info.test.lua b/test/sophia/monitoring.test.lua
similarity index 50%
rename from test/sophia/info.test.lua
rename to test/sophia/monitoring.test.lua
index b715e1097a55fe43b568dedea085fb4f340d8eb0..e15a49e8c4730cc4337f760e9073660eb83405ca 100644
--- a/test/sophia/info.test.lua
+++ b/test/sophia/monitoring.test.lua
@@ -1,10 +1,5 @@
--- box.info().sophia['sophia.version']
 
 space = box.schema.space.create('test', { engine = 'sophia' })
-index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
-
-for key = 1, 10 do space:insert({key}) end
-
--- box.info().sophia['db.100.index.count']
-
+index = space:create_index('primary', { type = 'tree', parts = {1, 'str'} })
+box.sophia()['sophia.version']
 space:drop()
diff --git a/test/sophia/options.result b/test/sophia/options.result
index 4249fd3862eb027730828d2108c946b35d499774..ad95bb9ad495014bad85d08a6536fce96c334225 100644
--- a/test/sophia/options.result
+++ b/test/sophia/options.result
@@ -2,10 +2,11 @@ box.cfg.sophia
 ---
 - page_size: 131072
   memory_limit: 0
-  threads: 0
+  compression_key: 0
+  threads: 3
   node_size: 134217728
   compression: none
 ...
-box.cfg.sophia.threads = 3
+box.cfg.sophia.threads = 5
 ---
 ...
diff --git a/test/sophia/options.test.lua b/test/sophia/options.test.lua
index 29ec8612ed8b606334f058f37bbdeb3dbb2c8434..9ae0b13abb2c36da4a71281b14e3ff44dfcaa3d0 100644
--- a/test/sophia/options.test.lua
+++ b/test/sophia/options.test.lua
@@ -1,3 +1,3 @@
 
 box.cfg.sophia
-box.cfg.sophia.threads = 3
+box.cfg.sophia.threads = 5
diff --git a/test/sophia/random.result b/test/sophia/random.result
deleted file mode 100644
index 56b896d871e1528ac386599c353ce860786ee534..0000000000000000000000000000000000000000
--- a/test/sophia/random.result
+++ /dev/null
@@ -1,20 +0,0 @@
--- random
-space = box.schema.space.create('test', { engine = 'sophia'})
----
-...
-index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
----
-...
-dofile('index_random_test.lua')
----
-...
-index_random_test(space, 'primary')
----
-- true
-...
-space:drop()
----
-...
-sophia_schedule()
----
-...
diff --git a/test/sophia/random.test.lua b/test/sophia/random.test.lua
deleted file mode 100644
index e3024025347e72dfe6c7a5dc6ce91f995d981768..0000000000000000000000000000000000000000
--- a/test/sophia/random.test.lua
+++ /dev/null
@@ -1,11 +0,0 @@
-
--- random
-
-space = box.schema.space.create('test', { engine = 'sophia'})
-index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
-
-dofile('index_random_test.lua')
-index_random_test(space, 'primary')
-
-space:drop()
-sophia_schedule()
diff --git a/test/sophia/recover_drop.result b/test/sophia/recover_drop.result
new file mode 100644
index 0000000000000000000000000000000000000000..631d5da89cac76b79888ab2a5059b0799b255f9a
--- /dev/null
+++ b/test/sophia/recover_drop.result
@@ -0,0 +1,564 @@
+-- recover dropped spaces
+name = string.match(arg[0], "([^,]+)%.lua")
+---
+...
+os.execute("rm -f " .. name .."/*.snap")
+---
+- 0
+...
+os.execute("rm -f " .. name .."/*.xlog")
+---
+- 0
+...
+--# stop server default
+--# start server default
+name = string.match(arg[0], "([^,]+)%.lua")
+---
+...
+os.execute("touch " .. name .."/lock")
+---
+- 0
+...
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary')
+---
+...
+for key = 1, 351 do space:insert({key}) end
+---
+...
+space:drop()
+---
+...
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary')
+---
+...
+for key = 500, 1000 do space:insert({key}) end
+---
+...
+--# stop server default
+--# start server default
+name = string.match(arg[0], "([^,]+)%.lua")
+---
+...
+os.execute("rm -f " .. name .."/lock")
+---
+- 0
+...
+space = box.space['test']
+---
+...
+index = space.index['primary']
+---
+...
+index:select({}, {iterator = box.index.ALL})
+---
+- - [500]
+  - [501]
+  - [502]
+  - [503]
+  - [504]
+  - [505]
+  - [506]
+  - [507]
+  - [508]
+  - [509]
+  - [510]
+  - [511]
+  - [512]
+  - [513]
+  - [514]
+  - [515]
+  - [516]
+  - [517]
+  - [518]
+  - [519]
+  - [520]
+  - [521]
+  - [522]
+  - [523]
+  - [524]
+  - [525]
+  - [526]
+  - [527]
+  - [528]
+  - [529]
+  - [530]
+  - [531]
+  - [532]
+  - [533]
+  - [534]
+  - [535]
+  - [536]
+  - [537]
+  - [538]
+  - [539]
+  - [540]
+  - [541]
+  - [542]
+  - [543]
+  - [544]
+  - [545]
+  - [546]
+  - [547]
+  - [548]
+  - [549]
+  - [550]
+  - [551]
+  - [552]
+  - [553]
+  - [554]
+  - [555]
+  - [556]
+  - [557]
+  - [558]
+  - [559]
+  - [560]
+  - [561]
+  - [562]
+  - [563]
+  - [564]
+  - [565]
+  - [566]
+  - [567]
+  - [568]
+  - [569]
+  - [570]
+  - [571]
+  - [572]
+  - [573]
+  - [574]
+  - [575]
+  - [576]
+  - [577]
+  - [578]
+  - [579]
+  - [580]
+  - [581]
+  - [582]
+  - [583]
+  - [584]
+  - [585]
+  - [586]
+  - [587]
+  - [588]
+  - [589]
+  - [590]
+  - [591]
+  - [592]
+  - [593]
+  - [594]
+  - [595]
+  - [596]
+  - [597]
+  - [598]
+  - [599]
+  - [600]
+  - [601]
+  - [602]
+  - [603]
+  - [604]
+  - [605]
+  - [606]
+  - [607]
+  - [608]
+  - [609]
+  - [610]
+  - [611]
+  - [612]
+  - [613]
+  - [614]
+  - [615]
+  - [616]
+  - [617]
+  - [618]
+  - [619]
+  - [620]
+  - [621]
+  - [622]
+  - [623]
+  - [624]
+  - [625]
+  - [626]
+  - [627]
+  - [628]
+  - [629]
+  - [630]
+  - [631]
+  - [632]
+  - [633]
+  - [634]
+  - [635]
+  - [636]
+  - [637]
+  - [638]
+  - [639]
+  - [640]
+  - [641]
+  - [642]
+  - [643]
+  - [644]
+  - [645]
+  - [646]
+  - [647]
+  - [648]
+  - [649]
+  - [650]
+  - [651]
+  - [652]
+  - [653]
+  - [654]
+  - [655]
+  - [656]
+  - [657]
+  - [658]
+  - [659]
+  - [660]
+  - [661]
+  - [662]
+  - [663]
+  - [664]
+  - [665]
+  - [666]
+  - [667]
+  - [668]
+  - [669]
+  - [670]
+  - [671]
+  - [672]
+  - [673]
+  - [674]
+  - [675]
+  - [676]
+  - [677]
+  - [678]
+  - [679]
+  - [680]
+  - [681]
+  - [682]
+  - [683]
+  - [684]
+  - [685]
+  - [686]
+  - [687]
+  - [688]
+  - [689]
+  - [690]
+  - [691]
+  - [692]
+  - [693]
+  - [694]
+  - [695]
+  - [696]
+  - [697]
+  - [698]
+  - [699]
+  - [700]
+  - [701]
+  - [702]
+  - [703]
+  - [704]
+  - [705]
+  - [706]
+  - [707]
+  - [708]
+  - [709]
+  - [710]
+  - [711]
+  - [712]
+  - [713]
+  - [714]
+  - [715]
+  - [716]
+  - [717]
+  - [718]
+  - [719]
+  - [720]
+  - [721]
+  - [722]
+  - [723]
+  - [724]
+  - [725]
+  - [726]
+  - [727]
+  - [728]
+  - [729]
+  - [730]
+  - [731]
+  - [732]
+  - [733]
+  - [734]
+  - [735]
+  - [736]
+  - [737]
+  - [738]
+  - [739]
+  - [740]
+  - [741]
+  - [742]
+  - [743]
+  - [744]
+  - [745]
+  - [746]
+  - [747]
+  - [748]
+  - [749]
+  - [750]
+  - [751]
+  - [752]
+  - [753]
+  - [754]
+  - [755]
+  - [756]
+  - [757]
+  - [758]
+  - [759]
+  - [760]
+  - [761]
+  - [762]
+  - [763]
+  - [764]
+  - [765]
+  - [766]
+  - [767]
+  - [768]
+  - [769]
+  - [770]
+  - [771]
+  - [772]
+  - [773]
+  - [774]
+  - [775]
+  - [776]
+  - [777]
+  - [778]
+  - [779]
+  - [780]
+  - [781]
+  - [782]
+  - [783]
+  - [784]
+  - [785]
+  - [786]
+  - [787]
+  - [788]
+  - [789]
+  - [790]
+  - [791]
+  - [792]
+  - [793]
+  - [794]
+  - [795]
+  - [796]
+  - [797]
+  - [798]
+  - [799]
+  - [800]
+  - [801]
+  - [802]
+  - [803]
+  - [804]
+  - [805]
+  - [806]
+  - [807]
+  - [808]
+  - [809]
+  - [810]
+  - [811]
+  - [812]
+  - [813]
+  - [814]
+  - [815]
+  - [816]
+  - [817]
+  - [818]
+  - [819]
+  - [820]
+  - [821]
+  - [822]
+  - [823]
+  - [824]
+  - [825]
+  - [826]
+  - [827]
+  - [828]
+  - [829]
+  - [830]
+  - [831]
+  - [832]
+  - [833]
+  - [834]
+  - [835]
+  - [836]
+  - [837]
+  - [838]
+  - [839]
+  - [840]
+  - [841]
+  - [842]
+  - [843]
+  - [844]
+  - [845]
+  - [846]
+  - [847]
+  - [848]
+  - [849]
+  - [850]
+  - [851]
+  - [852]
+  - [853]
+  - [854]
+  - [855]
+  - [856]
+  - [857]
+  - [858]
+  - [859]
+  - [860]
+  - [861]
+  - [862]
+  - [863]
+  - [864]
+  - [865]
+  - [866]
+  - [867]
+  - [868]
+  - [869]
+  - [870]
+  - [871]
+  - [872]
+  - [873]
+  - [874]
+  - [875]
+  - [876]
+  - [877]
+  - [878]
+  - [879]
+  - [880]
+  - [881]
+  - [882]
+  - [883]
+  - [884]
+  - [885]
+  - [886]
+  - [887]
+  - [888]
+  - [889]
+  - [890]
+  - [891]
+  - [892]
+  - [893]
+  - [894]
+  - [895]
+  - [896]
+  - [897]
+  - [898]
+  - [899]
+  - [900]
+  - [901]
+  - [902]
+  - [903]
+  - [904]
+  - [905]
+  - [906]
+  - [907]
+  - [908]
+  - [909]
+  - [910]
+  - [911]
+  - [912]
+  - [913]
+  - [914]
+  - [915]
+  - [916]
+  - [917]
+  - [918]
+  - [919]
+  - [920]
+  - [921]
+  - [922]
+  - [923]
+  - [924]
+  - [925]
+  - [926]
+  - [927]
+  - [928]
+  - [929]
+  - [930]
+  - [931]
+  - [932]
+  - [933]
+  - [934]
+  - [935]
+  - [936]
+  - [937]
+  - [938]
+  - [939]
+  - [940]
+  - [941]
+  - [942]
+  - [943]
+  - [944]
+  - [945]
+  - [946]
+  - [947]
+  - [948]
+  - [949]
+  - [950]
+  - [951]
+  - [952]
+  - [953]
+  - [954]
+  - [955]
+  - [956]
+  - [957]
+  - [958]
+  - [959]
+  - [960]
+  - [961]
+  - [962]
+  - [963]
+  - [964]
+  - [965]
+  - [966]
+  - [967]
+  - [968]
+  - [969]
+  - [970]
+  - [971]
+  - [972]
+  - [973]
+  - [974]
+  - [975]
+  - [976]
+  - [977]
+  - [978]
+  - [979]
+  - [980]
+  - [981]
+  - [982]
+  - [983]
+  - [984]
+  - [985]
+  - [986]
+  - [987]
+  - [988]
+  - [989]
+  - [990]
+  - [991]
+  - [992]
+  - [993]
+  - [994]
+  - [995]
+  - [996]
+  - [997]
+  - [998]
+  - [999]
+  - [1000]
+...
+space:drop()
+---
+...
diff --git a/test/sophia/recover_drop.test.lua b/test/sophia/recover_drop.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..86768d8be65d34a6e577bc3b42cb94d0b4a9de2a
--- /dev/null
+++ b/test/sophia/recover_drop.test.lua
@@ -0,0 +1,32 @@
+
+-- recover dropped spaces
+
+name = string.match(arg[0], "([^,]+)%.lua")
+os.execute("rm -f " .. name .."/*.snap")
+os.execute("rm -f " .. name .."/*.xlog")
+
+--# stop server default
+--# start server default
+
+name = string.match(arg[0], "([^,]+)%.lua")
+os.execute("touch " .. name .."/lock")
+
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary')
+for key = 1, 351 do space:insert({key}) end
+space:drop()
+
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary')
+for key = 500, 1000 do space:insert({key}) end
+
+--# stop server default
+--# start server default
+
+name = string.match(arg[0], "([^,]+)%.lua")
+os.execute("rm -f " .. name .."/lock")
+
+space = box.space['test']
+index = space.index['primary']
+index:select({}, {iterator = box.index.ALL})
+space:drop()
diff --git a/test/sophia/snapshot.result b/test/sophia/recover_snapshot.result
similarity index 95%
rename from test/sophia/snapshot.result
rename to test/sophia/recover_snapshot.result
index 1c77bb67a0884878c4eb69a2589ae5593b785963..f8929edd0124f35c9a0d66d347cb1cddfa7881b8 100644
--- a/test/sophia/snapshot.result
+++ b/test/sophia/recover_snapshot.result
@@ -1,4 +1,4 @@
--- snapshot
+-- write data recover from latest snapshot
 name = string.match(arg[0], "([^,]+)%.lua")
 ---
 ...
@@ -10,15 +10,15 @@ os.execute("rm -f " .. name .."/*.xlog")
 ---
 - 0
 ...
-os.execute("touch " .. name .."/mt")
----
-- 0
-...
 --# stop server default
 --# start server default
 name = string.match(arg[0], "([^,]+)%.lua")
 ---
 ...
+os.execute("touch " .. name .."/lock")
+---
+- 0
+...
 space = box.schema.space.create('test', { engine = 'sophia' })
 ---
 ...
@@ -32,29 +32,22 @@ box.snapshot()
 ---
 - ok
 ...
-os.execute("rm -f " .. name .."/mt")
----
-- 0
-...
-os.execute("touch " .. name .."/lock")
----
-- 0
-...
 --# stop server default
 --# start server default
 name = string.match(arg[0], "([^,]+)%.lua")
 ---
 ...
-space = box.space['test']
+os.execute("rm -f " .. name .."/lock")
 ---
+- 0
 ...
-t = {}
+space = box.space['test']
 ---
 ...
-for key = 1, 351 do table.insert(t, space:get({key})) end
+index = space.index['primary']
 ---
 ...
-t
+index:select({}, {iterator = box.index.ALL})
 ---
 - - [1]
   - [2]
@@ -411,7 +404,3 @@ t
 space:drop()
 ---
 ...
-os.execute("rm -f " .. name .."/lock")
----
-- 0
-...
diff --git a/test/sophia/snapshot.test.lua b/test/sophia/recover_snapshot.test.lua
similarity index 79%
rename from test/sophia/snapshot.test.lua
rename to test/sophia/recover_snapshot.test.lua
index 8d446fce8fba7cb4d305feaa62fb09969e6d5f12..8e23455edc6e922ebe4ad5915e2367d3a5317bab 100644
--- a/test/sophia/snapshot.test.lua
+++ b/test/sophia/recover_snapshot.test.lua
@@ -1,31 +1,29 @@
 
--- snapshot
+-- write data recover from latest snapshot
+
 name = string.match(arg[0], "([^,]+)%.lua")
 os.execute("rm -f " .. name .."/*.snap")
 os.execute("rm -f " .. name .."/*.xlog")
-os.execute("touch " .. name .."/mt")
 
 --# stop server default
 --# start server default
 
 name = string.match(arg[0], "([^,]+)%.lua")
+os.execute("touch " .. name .."/lock")
+
 space = box.schema.space.create('test', { engine = 'sophia' })
 index = space:create_index('primary')
 
 for key = 1, 351 do space:insert({key}) end
 box.snapshot()
 
-os.execute("rm -f " .. name .."/mt")
-os.execute("touch " .. name .."/lock")
-
 --# stop server default
 --# start server default
 
 name = string.match(arg[0], "([^,]+)%.lua")
+os.execute("rm -f " .. name .."/lock")
+
 space = box.space['test']
-t = {}
-for key = 1, 351 do table.insert(t, space:get({key})) end
-t
+index = space.index['primary']
+index:select({}, {iterator = box.index.ALL})
 space:drop()
-
-os.execute("rm -f " .. name .."/lock")
diff --git a/test/sophia/recover_snapshot_wal.result b/test/sophia/recover_snapshot_wal.result
new file mode 100644
index 0000000000000000000000000000000000000000..a0863e5d8837a691edd1901f94146a098a72c605
--- /dev/null
+++ b/test/sophia/recover_snapshot_wal.result
@@ -0,0 +1,1058 @@
+-- write data recover from latest snapshot and logs
+name = string.match(arg[0], "([^,]+)%.lua")
+---
+...
+os.execute("rm -f " .. name .."/*.snap")
+---
+- 0
+...
+os.execute("rm -f " .. name .."/*.xlog")
+---
+- 0
+...
+--# stop server default
+--# start server default
+name = string.match(arg[0], "([^,]+)%.lua")
+---
+...
+os.execute("touch " .. name .."/lock")
+---
+- 0
+...
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary')
+---
+...
+for key = 1, 351 do space:insert({key}) end
+---
+...
+box.snapshot()
+---
+- ok
+...
+for key = 352, 1000 do space:insert({key}) end
+---
+...
+--# stop server default
+--# start server default
+name = string.match(arg[0], "([^,]+)%.lua")
+---
+...
+os.execute("rm -f " .. name .."/lock")
+---
+- 0
+...
+space = box.space['test']
+---
+...
+index = space.index['primary']
+---
+...
+index:select({}, {iterator = box.index.ALL})
+---
+- - [1]
+  - [2]
+  - [3]
+  - [4]
+  - [5]
+  - [6]
+  - [7]
+  - [8]
+  - [9]
+  - [10]
+  - [11]
+  - [12]
+  - [13]
+  - [14]
+  - [15]
+  - [16]
+  - [17]
+  - [18]
+  - [19]
+  - [20]
+  - [21]
+  - [22]
+  - [23]
+  - [24]
+  - [25]
+  - [26]
+  - [27]
+  - [28]
+  - [29]
+  - [30]
+  - [31]
+  - [32]
+  - [33]
+  - [34]
+  - [35]
+  - [36]
+  - [37]
+  - [38]
+  - [39]
+  - [40]
+  - [41]
+  - [42]
+  - [43]
+  - [44]
+  - [45]
+  - [46]
+  - [47]
+  - [48]
+  - [49]
+  - [50]
+  - [51]
+  - [52]
+  - [53]
+  - [54]
+  - [55]
+  - [56]
+  - [57]
+  - [58]
+  - [59]
+  - [60]
+  - [61]
+  - [62]
+  - [63]
+  - [64]
+  - [65]
+  - [66]
+  - [67]
+  - [68]
+  - [69]
+  - [70]
+  - [71]
+  - [72]
+  - [73]
+  - [74]
+  - [75]
+  - [76]
+  - [77]
+  - [78]
+  - [79]
+  - [80]
+  - [81]
+  - [82]
+  - [83]
+  - [84]
+  - [85]
+  - [86]
+  - [87]
+  - [88]
+  - [89]
+  - [90]
+  - [91]
+  - [92]
+  - [93]
+  - [94]
+  - [95]
+  - [96]
+  - [97]
+  - [98]
+  - [99]
+  - [100]
+  - [101]
+  - [102]
+  - [103]
+  - [104]
+  - [105]
+  - [106]
+  - [107]
+  - [108]
+  - [109]
+  - [110]
+  - [111]
+  - [112]
+  - [113]
+  - [114]
+  - [115]
+  - [116]
+  - [117]
+  - [118]
+  - [119]
+  - [120]
+  - [121]
+  - [122]
+  - [123]
+  - [124]
+  - [125]
+  - [126]
+  - [127]
+  - [128]
+  - [129]
+  - [130]
+  - [131]
+  - [132]
+  - [133]
+  - [134]
+  - [135]
+  - [136]
+  - [137]
+  - [138]
+  - [139]
+  - [140]
+  - [141]
+  - [142]
+  - [143]
+  - [144]
+  - [145]
+  - [146]
+  - [147]
+  - [148]
+  - [149]
+  - [150]
+  - [151]
+  - [152]
+  - [153]
+  - [154]
+  - [155]
+  - [156]
+  - [157]
+  - [158]
+  - [159]
+  - [160]
+  - [161]
+  - [162]
+  - [163]
+  - [164]
+  - [165]
+  - [166]
+  - [167]
+  - [168]
+  - [169]
+  - [170]
+  - [171]
+  - [172]
+  - [173]
+  - [174]
+  - [175]
+  - [176]
+  - [177]
+  - [178]
+  - [179]
+  - [180]
+  - [181]
+  - [182]
+  - [183]
+  - [184]
+  - [185]
+  - [186]
+  - [187]
+  - [188]
+  - [189]
+  - [190]
+  - [191]
+  - [192]
+  - [193]
+  - [194]
+  - [195]
+  - [196]
+  - [197]
+  - [198]
+  - [199]
+  - [200]
+  - [201]
+  - [202]
+  - [203]
+  - [204]
+  - [205]
+  - [206]
+  - [207]
+  - [208]
+  - [209]
+  - [210]
+  - [211]
+  - [212]
+  - [213]
+  - [214]
+  - [215]
+  - [216]
+  - [217]
+  - [218]
+  - [219]
+  - [220]
+  - [221]
+  - [222]
+  - [223]
+  - [224]
+  - [225]
+  - [226]
+  - [227]
+  - [228]
+  - [229]
+  - [230]
+  - [231]
+  - [232]
+  - [233]
+  - [234]
+  - [235]
+  - [236]
+  - [237]
+  - [238]
+  - [239]
+  - [240]
+  - [241]
+  - [242]
+  - [243]
+  - [244]
+  - [245]
+  - [246]
+  - [247]
+  - [248]
+  - [249]
+  - [250]
+  - [251]
+  - [252]
+  - [253]
+  - [254]
+  - [255]
+  - [256]
+  - [257]
+  - [258]
+  - [259]
+  - [260]
+  - [261]
+  - [262]
+  - [263]
+  - [264]
+  - [265]
+  - [266]
+  - [267]
+  - [268]
+  - [269]
+  - [270]
+  - [271]
+  - [272]
+  - [273]
+  - [274]
+  - [275]
+  - [276]
+  - [277]
+  - [278]
+  - [279]
+  - [280]
+  - [281]
+  - [282]
+  - [283]
+  - [284]
+  - [285]
+  - [286]
+  - [287]
+  - [288]
+  - [289]
+  - [290]
+  - [291]
+  - [292]
+  - [293]
+  - [294]
+  - [295]
+  - [296]
+  - [297]
+  - [298]
+  - [299]
+  - [300]
+  - [301]
+  - [302]
+  - [303]
+  - [304]
+  - [305]
+  - [306]
+  - [307]
+  - [308]
+  - [309]
+  - [310]
+  - [311]
+  - [312]
+  - [313]
+  - [314]
+  - [315]
+  - [316]
+  - [317]
+  - [318]
+  - [319]
+  - [320]
+  - [321]
+  - [322]
+  - [323]
+  - [324]
+  - [325]
+  - [326]
+  - [327]
+  - [328]
+  - [329]
+  - [330]
+  - [331]
+  - [332]
+  - [333]
+  - [334]
+  - [335]
+  - [336]
+  - [337]
+  - [338]
+  - [339]
+  - [340]
+  - [341]
+  - [342]
+  - [343]
+  - [344]
+  - [345]
+  - [346]
+  - [347]
+  - [348]
+  - [349]
+  - [350]
+  - [351]
+  - [352]
+  - [353]
+  - [354]
+  - [355]
+  - [356]
+  - [357]
+  - [358]
+  - [359]
+  - [360]
+  - [361]
+  - [362]
+  - [363]
+  - [364]
+  - [365]
+  - [366]
+  - [367]
+  - [368]
+  - [369]
+  - [370]
+  - [371]
+  - [372]
+  - [373]
+  - [374]
+  - [375]
+  - [376]
+  - [377]
+  - [378]
+  - [379]
+  - [380]
+  - [381]
+  - [382]
+  - [383]
+  - [384]
+  - [385]
+  - [386]
+  - [387]
+  - [388]
+  - [389]
+  - [390]
+  - [391]
+  - [392]
+  - [393]
+  - [394]
+  - [395]
+  - [396]
+  - [397]
+  - [398]
+  - [399]
+  - [400]
+  - [401]
+  - [402]
+  - [403]
+  - [404]
+  - [405]
+  - [406]
+  - [407]
+  - [408]
+  - [409]
+  - [410]
+  - [411]
+  - [412]
+  - [413]
+  - [414]
+  - [415]
+  - [416]
+  - [417]
+  - [418]
+  - [419]
+  - [420]
+  - [421]
+  - [422]
+  - [423]
+  - [424]
+  - [425]
+  - [426]
+  - [427]
+  - [428]
+  - [429]
+  - [430]
+  - [431]
+  - [432]
+  - [433]
+  - [434]
+  - [435]
+  - [436]
+  - [437]
+  - [438]
+  - [439]
+  - [440]
+  - [441]
+  - [442]
+  - [443]
+  - [444]
+  - [445]
+  - [446]
+  - [447]
+  - [448]
+  - [449]
+  - [450]
+  - [451]
+  - [452]
+  - [453]
+  - [454]
+  - [455]
+  - [456]
+  - [457]
+  - [458]
+  - [459]
+  - [460]
+  - [461]
+  - [462]
+  - [463]
+  - [464]
+  - [465]
+  - [466]
+  - [467]
+  - [468]
+  - [469]
+  - [470]
+  - [471]
+  - [472]
+  - [473]
+  - [474]
+  - [475]
+  - [476]
+  - [477]
+  - [478]
+  - [479]
+  - [480]
+  - [481]
+  - [482]
+  - [483]
+  - [484]
+  - [485]
+  - [486]
+  - [487]
+  - [488]
+  - [489]
+  - [490]
+  - [491]
+  - [492]
+  - [493]
+  - [494]
+  - [495]
+  - [496]
+  - [497]
+  - [498]
+  - [499]
+  - [500]
+  - [501]
+  - [502]
+  - [503]
+  - [504]
+  - [505]
+  - [506]
+  - [507]
+  - [508]
+  - [509]
+  - [510]
+  - [511]
+  - [512]
+  - [513]
+  - [514]
+  - [515]
+  - [516]
+  - [517]
+  - [518]
+  - [519]
+  - [520]
+  - [521]
+  - [522]
+  - [523]
+  - [524]
+  - [525]
+  - [526]
+  - [527]
+  - [528]
+  - [529]
+  - [530]
+  - [531]
+  - [532]
+  - [533]
+  - [534]
+  - [535]
+  - [536]
+  - [537]
+  - [538]
+  - [539]
+  - [540]
+  - [541]
+  - [542]
+  - [543]
+  - [544]
+  - [545]
+  - [546]
+  - [547]
+  - [548]
+  - [549]
+  - [550]
+  - [551]
+  - [552]
+  - [553]
+  - [554]
+  - [555]
+  - [556]
+  - [557]
+  - [558]
+  - [559]
+  - [560]
+  - [561]
+  - [562]
+  - [563]
+  - [564]
+  - [565]
+  - [566]
+  - [567]
+  - [568]
+  - [569]
+  - [570]
+  - [571]
+  - [572]
+  - [573]
+  - [574]
+  - [575]
+  - [576]
+  - [577]
+  - [578]
+  - [579]
+  - [580]
+  - [581]
+  - [582]
+  - [583]
+  - [584]
+  - [585]
+  - [586]
+  - [587]
+  - [588]
+  - [589]
+  - [590]
+  - [591]
+  - [592]
+  - [593]
+  - [594]
+  - [595]
+  - [596]
+  - [597]
+  - [598]
+  - [599]
+  - [600]
+  - [601]
+  - [602]
+  - [603]
+  - [604]
+  - [605]
+  - [606]
+  - [607]
+  - [608]
+  - [609]
+  - [610]
+  - [611]
+  - [612]
+  - [613]
+  - [614]
+  - [615]
+  - [616]
+  - [617]
+  - [618]
+  - [619]
+  - [620]
+  - [621]
+  - [622]
+  - [623]
+  - [624]
+  - [625]
+  - [626]
+  - [627]
+  - [628]
+  - [629]
+  - [630]
+  - [631]
+  - [632]
+  - [633]
+  - [634]
+  - [635]
+  - [636]
+  - [637]
+  - [638]
+  - [639]
+  - [640]
+  - [641]
+  - [642]
+  - [643]
+  - [644]
+  - [645]
+  - [646]
+  - [647]
+  - [648]
+  - [649]
+  - [650]
+  - [651]
+  - [652]
+  - [653]
+  - [654]
+  - [655]
+  - [656]
+  - [657]
+  - [658]
+  - [659]
+  - [660]
+  - [661]
+  - [662]
+  - [663]
+  - [664]
+  - [665]
+  - [666]
+  - [667]
+  - [668]
+  - [669]
+  - [670]
+  - [671]
+  - [672]
+  - [673]
+  - [674]
+  - [675]
+  - [676]
+  - [677]
+  - [678]
+  - [679]
+  - [680]
+  - [681]
+  - [682]
+  - [683]
+  - [684]
+  - [685]
+  - [686]
+  - [687]
+  - [688]
+  - [689]
+  - [690]
+  - [691]
+  - [692]
+  - [693]
+  - [694]
+  - [695]
+  - [696]
+  - [697]
+  - [698]
+  - [699]
+  - [700]
+  - [701]
+  - [702]
+  - [703]
+  - [704]
+  - [705]
+  - [706]
+  - [707]
+  - [708]
+  - [709]
+  - [710]
+  - [711]
+  - [712]
+  - [713]
+  - [714]
+  - [715]
+  - [716]
+  - [717]
+  - [718]
+  - [719]
+  - [720]
+  - [721]
+  - [722]
+  - [723]
+  - [724]
+  - [725]
+  - [726]
+  - [727]
+  - [728]
+  - [729]
+  - [730]
+  - [731]
+  - [732]
+  - [733]
+  - [734]
+  - [735]
+  - [736]
+  - [737]
+  - [738]
+  - [739]
+  - [740]
+  - [741]
+  - [742]
+  - [743]
+  - [744]
+  - [745]
+  - [746]
+  - [747]
+  - [748]
+  - [749]
+  - [750]
+  - [751]
+  - [752]
+  - [753]
+  - [754]
+  - [755]
+  - [756]
+  - [757]
+  - [758]
+  - [759]
+  - [760]
+  - [761]
+  - [762]
+  - [763]
+  - [764]
+  - [765]
+  - [766]
+  - [767]
+  - [768]
+  - [769]
+  - [770]
+  - [771]
+  - [772]
+  - [773]
+  - [774]
+  - [775]
+  - [776]
+  - [777]
+  - [778]
+  - [779]
+  - [780]
+  - [781]
+  - [782]
+  - [783]
+  - [784]
+  - [785]
+  - [786]
+  - [787]
+  - [788]
+  - [789]
+  - [790]
+  - [791]
+  - [792]
+  - [793]
+  - [794]
+  - [795]
+  - [796]
+  - [797]
+  - [798]
+  - [799]
+  - [800]
+  - [801]
+  - [802]
+  - [803]
+  - [804]
+  - [805]
+  - [806]
+  - [807]
+  - [808]
+  - [809]
+  - [810]
+  - [811]
+  - [812]
+  - [813]
+  - [814]
+  - [815]
+  - [816]
+  - [817]
+  - [818]
+  - [819]
+  - [820]
+  - [821]
+  - [822]
+  - [823]
+  - [824]
+  - [825]
+  - [826]
+  - [827]
+  - [828]
+  - [829]
+  - [830]
+  - [831]
+  - [832]
+  - [833]
+  - [834]
+  - [835]
+  - [836]
+  - [837]
+  - [838]
+  - [839]
+  - [840]
+  - [841]
+  - [842]
+  - [843]
+  - [844]
+  - [845]
+  - [846]
+  - [847]
+  - [848]
+  - [849]
+  - [850]
+  - [851]
+  - [852]
+  - [853]
+  - [854]
+  - [855]
+  - [856]
+  - [857]
+  - [858]
+  - [859]
+  - [860]
+  - [861]
+  - [862]
+  - [863]
+  - [864]
+  - [865]
+  - [866]
+  - [867]
+  - [868]
+  - [869]
+  - [870]
+  - [871]
+  - [872]
+  - [873]
+  - [874]
+  - [875]
+  - [876]
+  - [877]
+  - [878]
+  - [879]
+  - [880]
+  - [881]
+  - [882]
+  - [883]
+  - [884]
+  - [885]
+  - [886]
+  - [887]
+  - [888]
+  - [889]
+  - [890]
+  - [891]
+  - [892]
+  - [893]
+  - [894]
+  - [895]
+  - [896]
+  - [897]
+  - [898]
+  - [899]
+  - [900]
+  - [901]
+  - [902]
+  - [903]
+  - [904]
+  - [905]
+  - [906]
+  - [907]
+  - [908]
+  - [909]
+  - [910]
+  - [911]
+  - [912]
+  - [913]
+  - [914]
+  - [915]
+  - [916]
+  - [917]
+  - [918]
+  - [919]
+  - [920]
+  - [921]
+  - [922]
+  - [923]
+  - [924]
+  - [925]
+  - [926]
+  - [927]
+  - [928]
+  - [929]
+  - [930]
+  - [931]
+  - [932]
+  - [933]
+  - [934]
+  - [935]
+  - [936]
+  - [937]
+  - [938]
+  - [939]
+  - [940]
+  - [941]
+  - [942]
+  - [943]
+  - [944]
+  - [945]
+  - [946]
+  - [947]
+  - [948]
+  - [949]
+  - [950]
+  - [951]
+  - [952]
+  - [953]
+  - [954]
+  - [955]
+  - [956]
+  - [957]
+  - [958]
+  - [959]
+  - [960]
+  - [961]
+  - [962]
+  - [963]
+  - [964]
+  - [965]
+  - [966]
+  - [967]
+  - [968]
+  - [969]
+  - [970]
+  - [971]
+  - [972]
+  - [973]
+  - [974]
+  - [975]
+  - [976]
+  - [977]
+  - [978]
+  - [979]
+  - [980]
+  - [981]
+  - [982]
+  - [983]
+  - [984]
+  - [985]
+  - [986]
+  - [987]
+  - [988]
+  - [989]
+  - [990]
+  - [991]
+  - [992]
+  - [993]
+  - [994]
+  - [995]
+  - [996]
+  - [997]
+  - [998]
+  - [999]
+  - [1000]
+...
+space:drop()
+---
+...
diff --git a/test/sophia/snapshot_view.test.lua b/test/sophia/recover_snapshot_wal.test.lua
similarity index 54%
rename from test/sophia/snapshot_view.test.lua
rename to test/sophia/recover_snapshot_wal.test.lua
index fd3666109389593e7ae567d2dd65092f2301646b..bb7d50e7f6c7ee48b8686710c0fe20818be30a0e 100644
--- a/test/sophia/snapshot_view.test.lua
+++ b/test/sophia/recover_snapshot_wal.test.lua
@@ -1,39 +1,31 @@
+
+-- write data recover from latest snapshot and logs
+
 name = string.match(arg[0], "([^,]+)%.lua")
 os.execute("rm -f " .. name .."/*.snap")
 os.execute("rm -f " .. name .."/*.xlog")
-os.execute("touch " .. name .."/mt")
 
 --# stop server default
 --# start server default
 
-space = box.schema.create_space('test', { engine = 'sophia' })
+name = string.match(arg[0], "([^,]+)%.lua")
+os.execute("touch " .. name .."/lock")
+
+space = box.schema.space.create('test', { engine = 'sophia' })
 index = space:create_index('primary')
 
 for key = 1, 351 do space:insert({key}) end
 box.snapshot()
-space:drop()
-sophia_schedule()
-name = string.match(arg[0], "([^,]+)%.lua")
--- remove tarantool xlogs
-os.execute("rm -f " .. name .."/*.xlog")
-os.execute("rm -f " .. name .."/mt")
-os.execute("touch " .. name .."/lock")
-sophia_rmdir()
+
+for key = 352, 1000 do space:insert({key}) end
 
 --# stop server default
 --# start server default
 
 name = string.match(arg[0], "([^,]+)%.lua")
-space = box.space['test']
-space:len()
-sophia_dir()[1]
-space:drop()
-sophia_schedule()
-sophia_dir()[1]
-
-os.execute("rm -f " .. name .."/*.snap")
-os.execute("rm -f " .. name .."/*.xlog")
 os.execute("rm -f " .. name .."/lock")
 
---# stop server default
---# start server default
+space = box.space['test']
+index = space.index['primary']
+index:select({}, {iterator = box.index.ALL})
+space:drop()
diff --git a/test/sophia/recover_wal.result b/test/sophia/recover_wal.result
new file mode 100644
index 0000000000000000000000000000000000000000..3892fd619bdafb185e46743a47b76c1a1f1e0488
--- /dev/null
+++ b/test/sophia/recover_wal.result
@@ -0,0 +1,1051 @@
+-- write data recover from logs only
+name = string.match(arg[0], "([^,]+)%.lua")
+---
+...
+os.execute("rm -f " .. name .."/*.snap")
+---
+- 0
+...
+os.execute("rm -f " .. name .."/*.xlog")
+---
+- 0
+...
+--# stop server default
+--# start server default
+name = string.match(arg[0], "([^,]+)%.lua")
+---
+...
+os.execute("touch " .. name .."/lock")
+---
+- 0
+...
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary')
+---
+...
+for key = 1, 1000 do space:insert({key}) end
+---
+...
+--# stop server default
+--# start server default
+name = string.match(arg[0], "([^,]+)%.lua")
+---
+...
+os.execute("rm -f " .. name .."/lock")
+---
+- 0
+...
+space = box.space['test']
+---
+...
+index = space.index['primary']
+---
+...
+index:select({}, {iterator = box.index.ALL})
+---
+- - [1]
+  - [2]
+  - [3]
+  - [4]
+  - [5]
+  - [6]
+  - [7]
+  - [8]
+  - [9]
+  - [10]
+  - [11]
+  - [12]
+  - [13]
+  - [14]
+  - [15]
+  - [16]
+  - [17]
+  - [18]
+  - [19]
+  - [20]
+  - [21]
+  - [22]
+  - [23]
+  - [24]
+  - [25]
+  - [26]
+  - [27]
+  - [28]
+  - [29]
+  - [30]
+  - [31]
+  - [32]
+  - [33]
+  - [34]
+  - [35]
+  - [36]
+  - [37]
+  - [38]
+  - [39]
+  - [40]
+  - [41]
+  - [42]
+  - [43]
+  - [44]
+  - [45]
+  - [46]
+  - [47]
+  - [48]
+  - [49]
+  - [50]
+  - [51]
+  - [52]
+  - [53]
+  - [54]
+  - [55]
+  - [56]
+  - [57]
+  - [58]
+  - [59]
+  - [60]
+  - [61]
+  - [62]
+  - [63]
+  - [64]
+  - [65]
+  - [66]
+  - [67]
+  - [68]
+  - [69]
+  - [70]
+  - [71]
+  - [72]
+  - [73]
+  - [74]
+  - [75]
+  - [76]
+  - [77]
+  - [78]
+  - [79]
+  - [80]
+  - [81]
+  - [82]
+  - [83]
+  - [84]
+  - [85]
+  - [86]
+  - [87]
+  - [88]
+  - [89]
+  - [90]
+  - [91]
+  - [92]
+  - [93]
+  - [94]
+  - [95]
+  - [96]
+  - [97]
+  - [98]
+  - [99]
+  - [100]
+  - [101]
+  - [102]
+  - [103]
+  - [104]
+  - [105]
+  - [106]
+  - [107]
+  - [108]
+  - [109]
+  - [110]
+  - [111]
+  - [112]
+  - [113]
+  - [114]
+  - [115]
+  - [116]
+  - [117]
+  - [118]
+  - [119]
+  - [120]
+  - [121]
+  - [122]
+  - [123]
+  - [124]
+  - [125]
+  - [126]
+  - [127]
+  - [128]
+  - [129]
+  - [130]
+  - [131]
+  - [132]
+  - [133]
+  - [134]
+  - [135]
+  - [136]
+  - [137]
+  - [138]
+  - [139]
+  - [140]
+  - [141]
+  - [142]
+  - [143]
+  - [144]
+  - [145]
+  - [146]
+  - [147]
+  - [148]
+  - [149]
+  - [150]
+  - [151]
+  - [152]
+  - [153]
+  - [154]
+  - [155]
+  - [156]
+  - [157]
+  - [158]
+  - [159]
+  - [160]
+  - [161]
+  - [162]
+  - [163]
+  - [164]
+  - [165]
+  - [166]
+  - [167]
+  - [168]
+  - [169]
+  - [170]
+  - [171]
+  - [172]
+  - [173]
+  - [174]
+  - [175]
+  - [176]
+  - [177]
+  - [178]
+  - [179]
+  - [180]
+  - [181]
+  - [182]
+  - [183]
+  - [184]
+  - [185]
+  - [186]
+  - [187]
+  - [188]
+  - [189]
+  - [190]
+  - [191]
+  - [192]
+  - [193]
+  - [194]
+  - [195]
+  - [196]
+  - [197]
+  - [198]
+  - [199]
+  - [200]
+  - [201]
+  - [202]
+  - [203]
+  - [204]
+  - [205]
+  - [206]
+  - [207]
+  - [208]
+  - [209]
+  - [210]
+  - [211]
+  - [212]
+  - [213]
+  - [214]
+  - [215]
+  - [216]
+  - [217]
+  - [218]
+  - [219]
+  - [220]
+  - [221]
+  - [222]
+  - [223]
+  - [224]
+  - [225]
+  - [226]
+  - [227]
+  - [228]
+  - [229]
+  - [230]
+  - [231]
+  - [232]
+  - [233]
+  - [234]
+  - [235]
+  - [236]
+  - [237]
+  - [238]
+  - [239]
+  - [240]
+  - [241]
+  - [242]
+  - [243]
+  - [244]
+  - [245]
+  - [246]
+  - [247]
+  - [248]
+  - [249]
+  - [250]
+  - [251]
+  - [252]
+  - [253]
+  - [254]
+  - [255]
+  - [256]
+  - [257]
+  - [258]
+  - [259]
+  - [260]
+  - [261]
+  - [262]
+  - [263]
+  - [264]
+  - [265]
+  - [266]
+  - [267]
+  - [268]
+  - [269]
+  - [270]
+  - [271]
+  - [272]
+  - [273]
+  - [274]
+  - [275]
+  - [276]
+  - [277]
+  - [278]
+  - [279]
+  - [280]
+  - [281]
+  - [282]
+  - [283]
+  - [284]
+  - [285]
+  - [286]
+  - [287]
+  - [288]
+  - [289]
+  - [290]
+  - [291]
+  - [292]
+  - [293]
+  - [294]
+  - [295]
+  - [296]
+  - [297]
+  - [298]
+  - [299]
+  - [300]
+  - [301]
+  - [302]
+  - [303]
+  - [304]
+  - [305]
+  - [306]
+  - [307]
+  - [308]
+  - [309]
+  - [310]
+  - [311]
+  - [312]
+  - [313]
+  - [314]
+  - [315]
+  - [316]
+  - [317]
+  - [318]
+  - [319]
+  - [320]
+  - [321]
+  - [322]
+  - [323]
+  - [324]
+  - [325]
+  - [326]
+  - [327]
+  - [328]
+  - [329]
+  - [330]
+  - [331]
+  - [332]
+  - [333]
+  - [334]
+  - [335]
+  - [336]
+  - [337]
+  - [338]
+  - [339]
+  - [340]
+  - [341]
+  - [342]
+  - [343]
+  - [344]
+  - [345]
+  - [346]
+  - [347]
+  - [348]
+  - [349]
+  - [350]
+  - [351]
+  - [352]
+  - [353]
+  - [354]
+  - [355]
+  - [356]
+  - [357]
+  - [358]
+  - [359]
+  - [360]
+  - [361]
+  - [362]
+  - [363]
+  - [364]
+  - [365]
+  - [366]
+  - [367]
+  - [368]
+  - [369]
+  - [370]
+  - [371]
+  - [372]
+  - [373]
+  - [374]
+  - [375]
+  - [376]
+  - [377]
+  - [378]
+  - [379]
+  - [380]
+  - [381]
+  - [382]
+  - [383]
+  - [384]
+  - [385]
+  - [386]
+  - [387]
+  - [388]
+  - [389]
+  - [390]
+  - [391]
+  - [392]
+  - [393]
+  - [394]
+  - [395]
+  - [396]
+  - [397]
+  - [398]
+  - [399]
+  - [400]
+  - [401]
+  - [402]
+  - [403]
+  - [404]
+  - [405]
+  - [406]
+  - [407]
+  - [408]
+  - [409]
+  - [410]
+  - [411]
+  - [412]
+  - [413]
+  - [414]
+  - [415]
+  - [416]
+  - [417]
+  - [418]
+  - [419]
+  - [420]
+  - [421]
+  - [422]
+  - [423]
+  - [424]
+  - [425]
+  - [426]
+  - [427]
+  - [428]
+  - [429]
+  - [430]
+  - [431]
+  - [432]
+  - [433]
+  - [434]
+  - [435]
+  - [436]
+  - [437]
+  - [438]
+  - [439]
+  - [440]
+  - [441]
+  - [442]
+  - [443]
+  - [444]
+  - [445]
+  - [446]
+  - [447]
+  - [448]
+  - [449]
+  - [450]
+  - [451]
+  - [452]
+  - [453]
+  - [454]
+  - [455]
+  - [456]
+  - [457]
+  - [458]
+  - [459]
+  - [460]
+  - [461]
+  - [462]
+  - [463]
+  - [464]
+  - [465]
+  - [466]
+  - [467]
+  - [468]
+  - [469]
+  - [470]
+  - [471]
+  - [472]
+  - [473]
+  - [474]
+  - [475]
+  - [476]
+  - [477]
+  - [478]
+  - [479]
+  - [480]
+  - [481]
+  - [482]
+  - [483]
+  - [484]
+  - [485]
+  - [486]
+  - [487]
+  - [488]
+  - [489]
+  - [490]
+  - [491]
+  - [492]
+  - [493]
+  - [494]
+  - [495]
+  - [496]
+  - [497]
+  - [498]
+  - [499]
+  - [500]
+  - [501]
+  - [502]
+  - [503]
+  - [504]
+  - [505]
+  - [506]
+  - [507]
+  - [508]
+  - [509]
+  - [510]
+  - [511]
+  - [512]
+  - [513]
+  - [514]
+  - [515]
+  - [516]
+  - [517]
+  - [518]
+  - [519]
+  - [520]
+  - [521]
+  - [522]
+  - [523]
+  - [524]
+  - [525]
+  - [526]
+  - [527]
+  - [528]
+  - [529]
+  - [530]
+  - [531]
+  - [532]
+  - [533]
+  - [534]
+  - [535]
+  - [536]
+  - [537]
+  - [538]
+  - [539]
+  - [540]
+  - [541]
+  - [542]
+  - [543]
+  - [544]
+  - [545]
+  - [546]
+  - [547]
+  - [548]
+  - [549]
+  - [550]
+  - [551]
+  - [552]
+  - [553]
+  - [554]
+  - [555]
+  - [556]
+  - [557]
+  - [558]
+  - [559]
+  - [560]
+  - [561]
+  - [562]
+  - [563]
+  - [564]
+  - [565]
+  - [566]
+  - [567]
+  - [568]
+  - [569]
+  - [570]
+  - [571]
+  - [572]
+  - [573]
+  - [574]
+  - [575]
+  - [576]
+  - [577]
+  - [578]
+  - [579]
+  - [580]
+  - [581]
+  - [582]
+  - [583]
+  - [584]
+  - [585]
+  - [586]
+  - [587]
+  - [588]
+  - [589]
+  - [590]
+  - [591]
+  - [592]
+  - [593]
+  - [594]
+  - [595]
+  - [596]
+  - [597]
+  - [598]
+  - [599]
+  - [600]
+  - [601]
+  - [602]
+  - [603]
+  - [604]
+  - [605]
+  - [606]
+  - [607]
+  - [608]
+  - [609]
+  - [610]
+  - [611]
+  - [612]
+  - [613]
+  - [614]
+  - [615]
+  - [616]
+  - [617]
+  - [618]
+  - [619]
+  - [620]
+  - [621]
+  - [622]
+  - [623]
+  - [624]
+  - [625]
+  - [626]
+  - [627]
+  - [628]
+  - [629]
+  - [630]
+  - [631]
+  - [632]
+  - [633]
+  - [634]
+  - [635]
+  - [636]
+  - [637]
+  - [638]
+  - [639]
+  - [640]
+  - [641]
+  - [642]
+  - [643]
+  - [644]
+  - [645]
+  - [646]
+  - [647]
+  - [648]
+  - [649]
+  - [650]
+  - [651]
+  - [652]
+  - [653]
+  - [654]
+  - [655]
+  - [656]
+  - [657]
+  - [658]
+  - [659]
+  - [660]
+  - [661]
+  - [662]
+  - [663]
+  - [664]
+  - [665]
+  - [666]
+  - [667]
+  - [668]
+  - [669]
+  - [670]
+  - [671]
+  - [672]
+  - [673]
+  - [674]
+  - [675]
+  - [676]
+  - [677]
+  - [678]
+  - [679]
+  - [680]
+  - [681]
+  - [682]
+  - [683]
+  - [684]
+  - [685]
+  - [686]
+  - [687]
+  - [688]
+  - [689]
+  - [690]
+  - [691]
+  - [692]
+  - [693]
+  - [694]
+  - [695]
+  - [696]
+  - [697]
+  - [698]
+  - [699]
+  - [700]
+  - [701]
+  - [702]
+  - [703]
+  - [704]
+  - [705]
+  - [706]
+  - [707]
+  - [708]
+  - [709]
+  - [710]
+  - [711]
+  - [712]
+  - [713]
+  - [714]
+  - [715]
+  - [716]
+  - [717]
+  - [718]
+  - [719]
+  - [720]
+  - [721]
+  - [722]
+  - [723]
+  - [724]
+  - [725]
+  - [726]
+  - [727]
+  - [728]
+  - [729]
+  - [730]
+  - [731]
+  - [732]
+  - [733]
+  - [734]
+  - [735]
+  - [736]
+  - [737]
+  - [738]
+  - [739]
+  - [740]
+  - [741]
+  - [742]
+  - [743]
+  - [744]
+  - [745]
+  - [746]
+  - [747]
+  - [748]
+  - [749]
+  - [750]
+  - [751]
+  - [752]
+  - [753]
+  - [754]
+  - [755]
+  - [756]
+  - [757]
+  - [758]
+  - [759]
+  - [760]
+  - [761]
+  - [762]
+  - [763]
+  - [764]
+  - [765]
+  - [766]
+  - [767]
+  - [768]
+  - [769]
+  - [770]
+  - [771]
+  - [772]
+  - [773]
+  - [774]
+  - [775]
+  - [776]
+  - [777]
+  - [778]
+  - [779]
+  - [780]
+  - [781]
+  - [782]
+  - [783]
+  - [784]
+  - [785]
+  - [786]
+  - [787]
+  - [788]
+  - [789]
+  - [790]
+  - [791]
+  - [792]
+  - [793]
+  - [794]
+  - [795]
+  - [796]
+  - [797]
+  - [798]
+  - [799]
+  - [800]
+  - [801]
+  - [802]
+  - [803]
+  - [804]
+  - [805]
+  - [806]
+  - [807]
+  - [808]
+  - [809]
+  - [810]
+  - [811]
+  - [812]
+  - [813]
+  - [814]
+  - [815]
+  - [816]
+  - [817]
+  - [818]
+  - [819]
+  - [820]
+  - [821]
+  - [822]
+  - [823]
+  - [824]
+  - [825]
+  - [826]
+  - [827]
+  - [828]
+  - [829]
+  - [830]
+  - [831]
+  - [832]
+  - [833]
+  - [834]
+  - [835]
+  - [836]
+  - [837]
+  - [838]
+  - [839]
+  - [840]
+  - [841]
+  - [842]
+  - [843]
+  - [844]
+  - [845]
+  - [846]
+  - [847]
+  - [848]
+  - [849]
+  - [850]
+  - [851]
+  - [852]
+  - [853]
+  - [854]
+  - [855]
+  - [856]
+  - [857]
+  - [858]
+  - [859]
+  - [860]
+  - [861]
+  - [862]
+  - [863]
+  - [864]
+  - [865]
+  - [866]
+  - [867]
+  - [868]
+  - [869]
+  - [870]
+  - [871]
+  - [872]
+  - [873]
+  - [874]
+  - [875]
+  - [876]
+  - [877]
+  - [878]
+  - [879]
+  - [880]
+  - [881]
+  - [882]
+  - [883]
+  - [884]
+  - [885]
+  - [886]
+  - [887]
+  - [888]
+  - [889]
+  - [890]
+  - [891]
+  - [892]
+  - [893]
+  - [894]
+  - [895]
+  - [896]
+  - [897]
+  - [898]
+  - [899]
+  - [900]
+  - [901]
+  - [902]
+  - [903]
+  - [904]
+  - [905]
+  - [906]
+  - [907]
+  - [908]
+  - [909]
+  - [910]
+  - [911]
+  - [912]
+  - [913]
+  - [914]
+  - [915]
+  - [916]
+  - [917]
+  - [918]
+  - [919]
+  - [920]
+  - [921]
+  - [922]
+  - [923]
+  - [924]
+  - [925]
+  - [926]
+  - [927]
+  - [928]
+  - [929]
+  - [930]
+  - [931]
+  - [932]
+  - [933]
+  - [934]
+  - [935]
+  - [936]
+  - [937]
+  - [938]
+  - [939]
+  - [940]
+  - [941]
+  - [942]
+  - [943]
+  - [944]
+  - [945]
+  - [946]
+  - [947]
+  - [948]
+  - [949]
+  - [950]
+  - [951]
+  - [952]
+  - [953]
+  - [954]
+  - [955]
+  - [956]
+  - [957]
+  - [958]
+  - [959]
+  - [960]
+  - [961]
+  - [962]
+  - [963]
+  - [964]
+  - [965]
+  - [966]
+  - [967]
+  - [968]
+  - [969]
+  - [970]
+  - [971]
+  - [972]
+  - [973]
+  - [974]
+  - [975]
+  - [976]
+  - [977]
+  - [978]
+  - [979]
+  - [980]
+  - [981]
+  - [982]
+  - [983]
+  - [984]
+  - [985]
+  - [986]
+  - [987]
+  - [988]
+  - [989]
+  - [990]
+  - [991]
+  - [992]
+  - [993]
+  - [994]
+  - [995]
+  - [996]
+  - [997]
+  - [998]
+  - [999]
+  - [1000]
+...
+space:drop()
+---
+...
diff --git a/test/sophia/recover_wal.test.lua b/test/sophia/recover_wal.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..3c3e08ee4a39b5e2d79c3cf3b03e3f01f88fc10b
--- /dev/null
+++ b/test/sophia/recover_wal.test.lua
@@ -0,0 +1,27 @@
+
+-- write data recover from logs only
+
+name = string.match(arg[0], "([^,]+)%.lua")
+os.execute("rm -f " .. name .."/*.snap")
+os.execute("rm -f " .. name .."/*.xlog")
+
+--# stop server default
+--# start server default
+
+name = string.match(arg[0], "([^,]+)%.lua")
+os.execute("touch " .. name .."/lock")
+
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary')
+for key = 1, 1000 do space:insert({key}) end
+
+--# stop server default
+--# start server default
+
+name = string.match(arg[0], "([^,]+)%.lua")
+os.execute("rm -f " .. name .."/lock")
+
+space = box.space['test']
+index = space.index['primary']
+index:select({}, {iterator = box.index.ALL})
+space:drop()
diff --git a/test/sophia/replace.result b/test/sophia/replace.result
new file mode 100644
index 0000000000000000000000000000000000000000..e77b46818e2d18f59558a532d9426dcb15bebc94
--- /dev/null
+++ b/test/sophia/replace.result
@@ -0,0 +1,375 @@
+-- replace (str)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'str'} })
+---
+...
+for key = 1, 100 do space:replace({tostring(key)}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({tostring(key)})) end
+---
+...
+t
+---
+- - ['1']
+  - ['2']
+  - ['3']
+  - ['4']
+  - ['5']
+  - ['6']
+  - ['7']
+  - ['8']
+  - ['9']
+  - ['10']
+  - ['11']
+  - ['12']
+  - ['13']
+  - ['14']
+  - ['15']
+  - ['16']
+  - ['17']
+  - ['18']
+  - ['19']
+  - ['20']
+  - ['21']
+  - ['22']
+  - ['23']
+  - ['24']
+  - ['25']
+  - ['26']
+  - ['27']
+  - ['28']
+  - ['29']
+  - ['30']
+  - ['31']
+  - ['32']
+  - ['33']
+  - ['34']
+  - ['35']
+  - ['36']
+  - ['37']
+  - ['38']
+  - ['39']
+  - ['40']
+  - ['41']
+  - ['42']
+  - ['43']
+  - ['44']
+  - ['45']
+  - ['46']
+  - ['47']
+  - ['48']
+  - ['49']
+  - ['50']
+  - ['51']
+  - ['52']
+  - ['53']
+  - ['54']
+  - ['55']
+  - ['56']
+  - ['57']
+  - ['58']
+  - ['59']
+  - ['60']
+  - ['61']
+  - ['62']
+  - ['63']
+  - ['64']
+  - ['65']
+  - ['66']
+  - ['67']
+  - ['68']
+  - ['69']
+  - ['70']
+  - ['71']
+  - ['72']
+  - ['73']
+  - ['74']
+  - ['75']
+  - ['76']
+  - ['77']
+  - ['78']
+  - ['79']
+  - ['80']
+  - ['81']
+  - ['82']
+  - ['83']
+  - ['84']
+  - ['85']
+  - ['86']
+  - ['87']
+  - ['88']
+  - ['89']
+  - ['90']
+  - ['91']
+  - ['92']
+  - ['93']
+  - ['94']
+  - ['95']
+  - ['96']
+  - ['97']
+  - ['98']
+  - ['99']
+  - ['100']
+...
+space:replace({tostring(7)})
+---
+...
+space:drop()
+---
+...
+-- replace (num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
+---
+...
+for key = 1, 100 do space:replace({key}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({key})) end
+---
+...
+t
+---
+- - [1]
+  - [2]
+  - [3]
+  - [4]
+  - [5]
+  - [6]
+  - [7]
+  - [8]
+  - [9]
+  - [10]
+  - [11]
+  - [12]
+  - [13]
+  - [14]
+  - [15]
+  - [16]
+  - [17]
+  - [18]
+  - [19]
+  - [20]
+  - [21]
+  - [22]
+  - [23]
+  - [24]
+  - [25]
+  - [26]
+  - [27]
+  - [28]
+  - [29]
+  - [30]
+  - [31]
+  - [32]
+  - [33]
+  - [34]
+  - [35]
+  - [36]
+  - [37]
+  - [38]
+  - [39]
+  - [40]
+  - [41]
+  - [42]
+  - [43]
+  - [44]
+  - [45]
+  - [46]
+  - [47]
+  - [48]
+  - [49]
+  - [50]
+  - [51]
+  - [52]
+  - [53]
+  - [54]
+  - [55]
+  - [56]
+  - [57]
+  - [58]
+  - [59]
+  - [60]
+  - [61]
+  - [62]
+  - [63]
+  - [64]
+  - [65]
+  - [66]
+  - [67]
+  - [68]
+  - [69]
+  - [70]
+  - [71]
+  - [72]
+  - [73]
+  - [74]
+  - [75]
+  - [76]
+  - [77]
+  - [78]
+  - [79]
+  - [80]
+  - [81]
+  - [82]
+  - [83]
+  - [84]
+  - [85]
+  - [86]
+  - [87]
+  - [88]
+  - [89]
+  - [90]
+  - [91]
+  - [92]
+  - [93]
+  - [94]
+  - [95]
+  - [96]
+  - [97]
+  - [98]
+  - [99]
+  - [100]
+...
+space:replace({7})
+---
+...
+space:drop()
+---
+...
+-- insert multi-part (num, num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num', 2, 'num'} })
+---
+...
+for key = 1, 100 do space:replace({key, key}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({key, key})) end
+---
+...
+t
+---
+- - [1, 1]
+  - [2, 2]
+  - [3, 3]
+  - [4, 4]
+  - [5, 5]
+  - [6, 6]
+  - [7, 7]
+  - [8, 8]
+  - [9, 9]
+  - [10, 10]
+  - [11, 11]
+  - [12, 12]
+  - [13, 13]
+  - [14, 14]
+  - [15, 15]
+  - [16, 16]
+  - [17, 17]
+  - [18, 18]
+  - [19, 19]
+  - [20, 20]
+  - [21, 21]
+  - [22, 22]
+  - [23, 23]
+  - [24, 24]
+  - [25, 25]
+  - [26, 26]
+  - [27, 27]
+  - [28, 28]
+  - [29, 29]
+  - [30, 30]
+  - [31, 31]
+  - [32, 32]
+  - [33, 33]
+  - [34, 34]
+  - [35, 35]
+  - [36, 36]
+  - [37, 37]
+  - [38, 38]
+  - [39, 39]
+  - [40, 40]
+  - [41, 41]
+  - [42, 42]
+  - [43, 43]
+  - [44, 44]
+  - [45, 45]
+  - [46, 46]
+  - [47, 47]
+  - [48, 48]
+  - [49, 49]
+  - [50, 50]
+  - [51, 51]
+  - [52, 52]
+  - [53, 53]
+  - [54, 54]
+  - [55, 55]
+  - [56, 56]
+  - [57, 57]
+  - [58, 58]
+  - [59, 59]
+  - [60, 60]
+  - [61, 61]
+  - [62, 62]
+  - [63, 63]
+  - [64, 64]
+  - [65, 65]
+  - [66, 66]
+  - [67, 67]
+  - [68, 68]
+  - [69, 69]
+  - [70, 70]
+  - [71, 71]
+  - [72, 72]
+  - [73, 73]
+  - [74, 74]
+  - [75, 75]
+  - [76, 76]
+  - [77, 77]
+  - [78, 78]
+  - [79, 79]
+  - [80, 80]
+  - [81, 81]
+  - [82, 82]
+  - [83, 83]
+  - [84, 84]
+  - [85, 85]
+  - [86, 86]
+  - [87, 87]
+  - [88, 88]
+  - [89, 89]
+  - [90, 90]
+  - [91, 91]
+  - [92, 92]
+  - [93, 93]
+  - [94, 94]
+  - [95, 95]
+  - [96, 96]
+  - [97, 97]
+  - [98, 98]
+  - [99, 99]
+  - [100, 100]
+...
+space:replace({7, 7})
+---
+...
+space:drop()
+---
+...
diff --git a/test/sophia/replace.test.lua b/test/sophia/replace.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..03583a2473a43d947b943393c2cdc2bfac3d8fd5
--- /dev/null
+++ b/test/sophia/replace.test.lua
@@ -0,0 +1,32 @@
+
+-- replace (str)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'str'} })
+for key = 1, 100 do space:replace({tostring(key)}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({tostring(key)})) end
+t
+space:replace({tostring(7)})
+space:drop()
+
+
+-- replace (num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
+for key = 1, 100 do space:replace({key}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({key})) end
+t
+space:replace({7})
+space:drop()
+
+
+-- insert multi-part (num, num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num', 2, 'num'} })
+for key = 1, 100 do space:replace({key, key}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({key, key})) end
+t
+space:replace({7, 7})
+space:drop()
diff --git a/test/sophia/select.result b/test/sophia/select.result
new file mode 100644
index 0000000000000000000000000000000000000000..a02c96208a2fe9f4360d50a9843ebebe7bd85ecc
--- /dev/null
+++ b/test/sophia/select.result
@@ -0,0 +1,2424 @@
+-- select (str)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'str'} })
+---
+...
+for key = 1, 100 do space:replace({tostring(key)}) end
+---
+...
+index:select({}, {iterator = box.index.ALL})
+---
+- - ['1']
+  - ['10']
+  - ['100']
+  - ['11']
+  - ['12']
+  - ['13']
+  - ['14']
+  - ['15']
+  - ['16']
+  - ['17']
+  - ['18']
+  - ['19']
+  - ['2']
+  - ['20']
+  - ['21']
+  - ['22']
+  - ['23']
+  - ['24']
+  - ['25']
+  - ['26']
+  - ['27']
+  - ['28']
+  - ['29']
+  - ['3']
+  - ['30']
+  - ['31']
+  - ['32']
+  - ['33']
+  - ['34']
+  - ['35']
+  - ['36']
+  - ['37']
+  - ['38']
+  - ['39']
+  - ['4']
+  - ['40']
+  - ['41']
+  - ['42']
+  - ['43']
+  - ['44']
+  - ['45']
+  - ['46']
+  - ['47']
+  - ['48']
+  - ['49']
+  - ['5']
+  - ['50']
+  - ['51']
+  - ['52']
+  - ['53']
+  - ['54']
+  - ['55']
+  - ['56']
+  - ['57']
+  - ['58']
+  - ['59']
+  - ['6']
+  - ['60']
+  - ['61']
+  - ['62']
+  - ['63']
+  - ['64']
+  - ['65']
+  - ['66']
+  - ['67']
+  - ['68']
+  - ['69']
+  - ['7']
+  - ['70']
+  - ['71']
+  - ['72']
+  - ['73']
+  - ['74']
+  - ['75']
+  - ['76']
+  - ['77']
+  - ['78']
+  - ['79']
+  - ['8']
+  - ['80']
+  - ['81']
+  - ['82']
+  - ['83']
+  - ['84']
+  - ['85']
+  - ['86']
+  - ['87']
+  - ['88']
+  - ['89']
+  - ['9']
+  - ['90']
+  - ['91']
+  - ['92']
+  - ['93']
+  - ['94']
+  - ['95']
+  - ['96']
+  - ['97']
+  - ['98']
+  - ['99']
+...
+index:select({}, {iterator = box.index.GE})
+---
+- - ['1']
+  - ['10']
+  - ['100']
+  - ['11']
+  - ['12']
+  - ['13']
+  - ['14']
+  - ['15']
+  - ['16']
+  - ['17']
+  - ['18']
+  - ['19']
+  - ['2']
+  - ['20']
+  - ['21']
+  - ['22']
+  - ['23']
+  - ['24']
+  - ['25']
+  - ['26']
+  - ['27']
+  - ['28']
+  - ['29']
+  - ['3']
+  - ['30']
+  - ['31']
+  - ['32']
+  - ['33']
+  - ['34']
+  - ['35']
+  - ['36']
+  - ['37']
+  - ['38']
+  - ['39']
+  - ['4']
+  - ['40']
+  - ['41']
+  - ['42']
+  - ['43']
+  - ['44']
+  - ['45']
+  - ['46']
+  - ['47']
+  - ['48']
+  - ['49']
+  - ['5']
+  - ['50']
+  - ['51']
+  - ['52']
+  - ['53']
+  - ['54']
+  - ['55']
+  - ['56']
+  - ['57']
+  - ['58']
+  - ['59']
+  - ['6']
+  - ['60']
+  - ['61']
+  - ['62']
+  - ['63']
+  - ['64']
+  - ['65']
+  - ['66']
+  - ['67']
+  - ['68']
+  - ['69']
+  - ['7']
+  - ['70']
+  - ['71']
+  - ['72']
+  - ['73']
+  - ['74']
+  - ['75']
+  - ['76']
+  - ['77']
+  - ['78']
+  - ['79']
+  - ['8']
+  - ['80']
+  - ['81']
+  - ['82']
+  - ['83']
+  - ['84']
+  - ['85']
+  - ['86']
+  - ['87']
+  - ['88']
+  - ['89']
+  - ['9']
+  - ['90']
+  - ['91']
+  - ['92']
+  - ['93']
+  - ['94']
+  - ['95']
+  - ['96']
+  - ['97']
+  - ['98']
+  - ['99']
+...
+index:select(tostring(44), {iterator = box.index.GE})
+---
+- - ['44']
+  - ['45']
+  - ['46']
+  - ['47']
+  - ['48']
+  - ['49']
+  - ['5']
+  - ['50']
+  - ['51']
+  - ['52']
+  - ['53']
+  - ['54']
+  - ['55']
+  - ['56']
+  - ['57']
+  - ['58']
+  - ['59']
+  - ['6']
+  - ['60']
+  - ['61']
+  - ['62']
+  - ['63']
+  - ['64']
+  - ['65']
+  - ['66']
+  - ['67']
+  - ['68']
+  - ['69']
+  - ['7']
+  - ['70']
+  - ['71']
+  - ['72']
+  - ['73']
+  - ['74']
+  - ['75']
+  - ['76']
+  - ['77']
+  - ['78']
+  - ['79']
+  - ['8']
+  - ['80']
+  - ['81']
+  - ['82']
+  - ['83']
+  - ['84']
+  - ['85']
+  - ['86']
+  - ['87']
+  - ['88']
+  - ['89']
+  - ['9']
+  - ['90']
+  - ['91']
+  - ['92']
+  - ['93']
+  - ['94']
+  - ['95']
+  - ['96']
+  - ['97']
+  - ['98']
+  - ['99']
+...
+index:select({}, {iterator = box.index.GT})
+---
+- - ['1']
+  - ['10']
+  - ['100']
+  - ['11']
+  - ['12']
+  - ['13']
+  - ['14']
+  - ['15']
+  - ['16']
+  - ['17']
+  - ['18']
+  - ['19']
+  - ['2']
+  - ['20']
+  - ['21']
+  - ['22']
+  - ['23']
+  - ['24']
+  - ['25']
+  - ['26']
+  - ['27']
+  - ['28']
+  - ['29']
+  - ['3']
+  - ['30']
+  - ['31']
+  - ['32']
+  - ['33']
+  - ['34']
+  - ['35']
+  - ['36']
+  - ['37']
+  - ['38']
+  - ['39']
+  - ['4']
+  - ['40']
+  - ['41']
+  - ['42']
+  - ['43']
+  - ['44']
+  - ['45']
+  - ['46']
+  - ['47']
+  - ['48']
+  - ['49']
+  - ['5']
+  - ['50']
+  - ['51']
+  - ['52']
+  - ['53']
+  - ['54']
+  - ['55']
+  - ['56']
+  - ['57']
+  - ['58']
+  - ['59']
+  - ['6']
+  - ['60']
+  - ['61']
+  - ['62']
+  - ['63']
+  - ['64']
+  - ['65']
+  - ['66']
+  - ['67']
+  - ['68']
+  - ['69']
+  - ['7']
+  - ['70']
+  - ['71']
+  - ['72']
+  - ['73']
+  - ['74']
+  - ['75']
+  - ['76']
+  - ['77']
+  - ['78']
+  - ['79']
+  - ['8']
+  - ['80']
+  - ['81']
+  - ['82']
+  - ['83']
+  - ['84']
+  - ['85']
+  - ['86']
+  - ['87']
+  - ['88']
+  - ['89']
+  - ['9']
+  - ['90']
+  - ['91']
+  - ['92']
+  - ['93']
+  - ['94']
+  - ['95']
+  - ['96']
+  - ['97']
+  - ['98']
+  - ['99']
+...
+index:select(tostring(44), {iterator = box.index.GT})
+---
+- - ['45']
+  - ['46']
+  - ['47']
+  - ['48']
+  - ['49']
+  - ['5']
+  - ['50']
+  - ['51']
+  - ['52']
+  - ['53']
+  - ['54']
+  - ['55']
+  - ['56']
+  - ['57']
+  - ['58']
+  - ['59']
+  - ['6']
+  - ['60']
+  - ['61']
+  - ['62']
+  - ['63']
+  - ['64']
+  - ['65']
+  - ['66']
+  - ['67']
+  - ['68']
+  - ['69']
+  - ['7']
+  - ['70']
+  - ['71']
+  - ['72']
+  - ['73']
+  - ['74']
+  - ['75']
+  - ['76']
+  - ['77']
+  - ['78']
+  - ['79']
+  - ['8']
+  - ['80']
+  - ['81']
+  - ['82']
+  - ['83']
+  - ['84']
+  - ['85']
+  - ['86']
+  - ['87']
+  - ['88']
+  - ['89']
+  - ['9']
+  - ['90']
+  - ['91']
+  - ['92']
+  - ['93']
+  - ['94']
+  - ['95']
+  - ['96']
+  - ['97']
+  - ['98']
+  - ['99']
+...
+index:select({}, {iterator = box.index.LE})
+---
+- - ['99']
+  - ['98']
+  - ['97']
+  - ['96']
+  - ['95']
+  - ['94']
+  - ['93']
+  - ['92']
+  - ['91']
+  - ['90']
+  - ['9']
+  - ['89']
+  - ['88']
+  - ['87']
+  - ['86']
+  - ['85']
+  - ['84']
+  - ['83']
+  - ['82']
+  - ['81']
+  - ['80']
+  - ['8']
+  - ['79']
+  - ['78']
+  - ['77']
+  - ['76']
+  - ['75']
+  - ['74']
+  - ['73']
+  - ['72']
+  - ['71']
+  - ['70']
+  - ['7']
+  - ['69']
+  - ['68']
+  - ['67']
+  - ['66']
+  - ['65']
+  - ['64']
+  - ['63']
+  - ['62']
+  - ['61']
+  - ['60']
+  - ['6']
+  - ['59']
+  - ['58']
+  - ['57']
+  - ['56']
+  - ['55']
+  - ['54']
+  - ['53']
+  - ['52']
+  - ['51']
+  - ['50']
+  - ['5']
+  - ['49']
+  - ['48']
+  - ['47']
+  - ['46']
+  - ['45']
+  - ['44']
+  - ['43']
+  - ['42']
+  - ['41']
+  - ['40']
+  - ['4']
+  - ['39']
+  - ['38']
+  - ['37']
+  - ['36']
+  - ['35']
+  - ['34']
+  - ['33']
+  - ['32']
+  - ['31']
+  - ['30']
+  - ['3']
+  - ['29']
+  - ['28']
+  - ['27']
+  - ['26']
+  - ['25']
+  - ['24']
+  - ['23']
+  - ['22']
+  - ['21']
+  - ['20']
+  - ['2']
+  - ['19']
+  - ['18']
+  - ['17']
+  - ['16']
+  - ['15']
+  - ['14']
+  - ['13']
+  - ['12']
+  - ['11']
+  - ['100']
+  - ['10']
+  - ['1']
+...
+index:select(tostring(77), {iterator = box.index.LE})
+---
+- - ['77']
+  - ['76']
+  - ['75']
+  - ['74']
+  - ['73']
+  - ['72']
+  - ['71']
+  - ['70']
+  - ['7']
+  - ['69']
+  - ['68']
+  - ['67']
+  - ['66']
+  - ['65']
+  - ['64']
+  - ['63']
+  - ['62']
+  - ['61']
+  - ['60']
+  - ['6']
+  - ['59']
+  - ['58']
+  - ['57']
+  - ['56']
+  - ['55']
+  - ['54']
+  - ['53']
+  - ['52']
+  - ['51']
+  - ['50']
+  - ['5']
+  - ['49']
+  - ['48']
+  - ['47']
+  - ['46']
+  - ['45']
+  - ['44']
+  - ['43']
+  - ['42']
+  - ['41']
+  - ['40']
+  - ['4']
+  - ['39']
+  - ['38']
+  - ['37']
+  - ['36']
+  - ['35']
+  - ['34']
+  - ['33']
+  - ['32']
+  - ['31']
+  - ['30']
+  - ['3']
+  - ['29']
+  - ['28']
+  - ['27']
+  - ['26']
+  - ['25']
+  - ['24']
+  - ['23']
+  - ['22']
+  - ['21']
+  - ['20']
+  - ['2']
+  - ['19']
+  - ['18']
+  - ['17']
+  - ['16']
+  - ['15']
+  - ['14']
+  - ['13']
+  - ['12']
+  - ['11']
+  - ['100']
+  - ['10']
+  - ['1']
+...
+index:select({}, {iterator = box.index.LT})
+---
+- - ['99']
+  - ['98']
+  - ['97']
+  - ['96']
+  - ['95']
+  - ['94']
+  - ['93']
+  - ['92']
+  - ['91']
+  - ['90']
+  - ['9']
+  - ['89']
+  - ['88']
+  - ['87']
+  - ['86']
+  - ['85']
+  - ['84']
+  - ['83']
+  - ['82']
+  - ['81']
+  - ['80']
+  - ['8']
+  - ['79']
+  - ['78']
+  - ['77']
+  - ['76']
+  - ['75']
+  - ['74']
+  - ['73']
+  - ['72']
+  - ['71']
+  - ['70']
+  - ['7']
+  - ['69']
+  - ['68']
+  - ['67']
+  - ['66']
+  - ['65']
+  - ['64']
+  - ['63']
+  - ['62']
+  - ['61']
+  - ['60']
+  - ['6']
+  - ['59']
+  - ['58']
+  - ['57']
+  - ['56']
+  - ['55']
+  - ['54']
+  - ['53']
+  - ['52']
+  - ['51']
+  - ['50']
+  - ['5']
+  - ['49']
+  - ['48']
+  - ['47']
+  - ['46']
+  - ['45']
+  - ['44']
+  - ['43']
+  - ['42']
+  - ['41']
+  - ['40']
+  - ['4']
+  - ['39']
+  - ['38']
+  - ['37']
+  - ['36']
+  - ['35']
+  - ['34']
+  - ['33']
+  - ['32']
+  - ['31']
+  - ['30']
+  - ['3']
+  - ['29']
+  - ['28']
+  - ['27']
+  - ['26']
+  - ['25']
+  - ['24']
+  - ['23']
+  - ['22']
+  - ['21']
+  - ['20']
+  - ['2']
+  - ['19']
+  - ['18']
+  - ['17']
+  - ['16']
+  - ['15']
+  - ['14']
+  - ['13']
+  - ['12']
+  - ['11']
+  - ['100']
+  - ['10']
+  - ['1']
+...
+index:select(tostring(77), {iterator = box.index.LT})
+---
+- - ['76']
+  - ['75']
+  - ['74']
+  - ['73']
+  - ['72']
+  - ['71']
+  - ['70']
+  - ['7']
+  - ['69']
+  - ['68']
+  - ['67']
+  - ['66']
+  - ['65']
+  - ['64']
+  - ['63']
+  - ['62']
+  - ['61']
+  - ['60']
+  - ['6']
+  - ['59']
+  - ['58']
+  - ['57']
+  - ['56']
+  - ['55']
+  - ['54']
+  - ['53']
+  - ['52']
+  - ['51']
+  - ['50']
+  - ['5']
+  - ['49']
+  - ['48']
+  - ['47']
+  - ['46']
+  - ['45']
+  - ['44']
+  - ['43']
+  - ['42']
+  - ['41']
+  - ['40']
+  - ['4']
+  - ['39']
+  - ['38']
+  - ['37']
+  - ['36']
+  - ['35']
+  - ['34']
+  - ['33']
+  - ['32']
+  - ['31']
+  - ['30']
+  - ['3']
+  - ['29']
+  - ['28']
+  - ['27']
+  - ['26']
+  - ['25']
+  - ['24']
+  - ['23']
+  - ['22']
+  - ['21']
+  - ['20']
+  - ['2']
+  - ['19']
+  - ['18']
+  - ['17']
+  - ['16']
+  - ['15']
+  - ['14']
+  - ['13']
+  - ['12']
+  - ['11']
+  - ['100']
+  - ['10']
+  - ['1']
+...
+space:drop()
+---
+...
+-- select (num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
+---
+...
+for key = 1, 100 do space:replace({key}) end
+---
+...
+index:select({}, {iterator = box.index.ALL})
+---
+- - [1]
+  - [2]
+  - [3]
+  - [4]
+  - [5]
+  - [6]
+  - [7]
+  - [8]
+  - [9]
+  - [10]
+  - [11]
+  - [12]
+  - [13]
+  - [14]
+  - [15]
+  - [16]
+  - [17]
+  - [18]
+  - [19]
+  - [20]
+  - [21]
+  - [22]
+  - [23]
+  - [24]
+  - [25]
+  - [26]
+  - [27]
+  - [28]
+  - [29]
+  - [30]
+  - [31]
+  - [32]
+  - [33]
+  - [34]
+  - [35]
+  - [36]
+  - [37]
+  - [38]
+  - [39]
+  - [40]
+  - [41]
+  - [42]
+  - [43]
+  - [44]
+  - [45]
+  - [46]
+  - [47]
+  - [48]
+  - [49]
+  - [50]
+  - [51]
+  - [52]
+  - [53]
+  - [54]
+  - [55]
+  - [56]
+  - [57]
+  - [58]
+  - [59]
+  - [60]
+  - [61]
+  - [62]
+  - [63]
+  - [64]
+  - [65]
+  - [66]
+  - [67]
+  - [68]
+  - [69]
+  - [70]
+  - [71]
+  - [72]
+  - [73]
+  - [74]
+  - [75]
+  - [76]
+  - [77]
+  - [78]
+  - [79]
+  - [80]
+  - [81]
+  - [82]
+  - [83]
+  - [84]
+  - [85]
+  - [86]
+  - [87]
+  - [88]
+  - [89]
+  - [90]
+  - [91]
+  - [92]
+  - [93]
+  - [94]
+  - [95]
+  - [96]
+  - [97]
+  - [98]
+  - [99]
+  - [100]
+...
+index:select({}, {iterator = box.index.GE})
+---
+- - [1]
+  - [2]
+  - [3]
+  - [4]
+  - [5]
+  - [6]
+  - [7]
+  - [8]
+  - [9]
+  - [10]
+  - [11]
+  - [12]
+  - [13]
+  - [14]
+  - [15]
+  - [16]
+  - [17]
+  - [18]
+  - [19]
+  - [20]
+  - [21]
+  - [22]
+  - [23]
+  - [24]
+  - [25]
+  - [26]
+  - [27]
+  - [28]
+  - [29]
+  - [30]
+  - [31]
+  - [32]
+  - [33]
+  - [34]
+  - [35]
+  - [36]
+  - [37]
+  - [38]
+  - [39]
+  - [40]
+  - [41]
+  - [42]
+  - [43]
+  - [44]
+  - [45]
+  - [46]
+  - [47]
+  - [48]
+  - [49]
+  - [50]
+  - [51]
+  - [52]
+  - [53]
+  - [54]
+  - [55]
+  - [56]
+  - [57]
+  - [58]
+  - [59]
+  - [60]
+  - [61]
+  - [62]
+  - [63]
+  - [64]
+  - [65]
+  - [66]
+  - [67]
+  - [68]
+  - [69]
+  - [70]
+  - [71]
+  - [72]
+  - [73]
+  - [74]
+  - [75]
+  - [76]
+  - [77]
+  - [78]
+  - [79]
+  - [80]
+  - [81]
+  - [82]
+  - [83]
+  - [84]
+  - [85]
+  - [86]
+  - [87]
+  - [88]
+  - [89]
+  - [90]
+  - [91]
+  - [92]
+  - [93]
+  - [94]
+  - [95]
+  - [96]
+  - [97]
+  - [98]
+  - [99]
+  - [100]
+...
+index:select(44, {iterator = box.index.GE})
+---
+- - [44]
+  - [45]
+  - [46]
+  - [47]
+  - [48]
+  - [49]
+  - [50]
+  - [51]
+  - [52]
+  - [53]
+  - [54]
+  - [55]
+  - [56]
+  - [57]
+  - [58]
+  - [59]
+  - [60]
+  - [61]
+  - [62]
+  - [63]
+  - [64]
+  - [65]
+  - [66]
+  - [67]
+  - [68]
+  - [69]
+  - [70]
+  - [71]
+  - [72]
+  - [73]
+  - [74]
+  - [75]
+  - [76]
+  - [77]
+  - [78]
+  - [79]
+  - [80]
+  - [81]
+  - [82]
+  - [83]
+  - [84]
+  - [85]
+  - [86]
+  - [87]
+  - [88]
+  - [89]
+  - [90]
+  - [91]
+  - [92]
+  - [93]
+  - [94]
+  - [95]
+  - [96]
+  - [97]
+  - [98]
+  - [99]
+  - [100]
+...
+index:select({}, {iterator = box.index.GT})
+---
+- - [1]
+  - [2]
+  - [3]
+  - [4]
+  - [5]
+  - [6]
+  - [7]
+  - [8]
+  - [9]
+  - [10]
+  - [11]
+  - [12]
+  - [13]
+  - [14]
+  - [15]
+  - [16]
+  - [17]
+  - [18]
+  - [19]
+  - [20]
+  - [21]
+  - [22]
+  - [23]
+  - [24]
+  - [25]
+  - [26]
+  - [27]
+  - [28]
+  - [29]
+  - [30]
+  - [31]
+  - [32]
+  - [33]
+  - [34]
+  - [35]
+  - [36]
+  - [37]
+  - [38]
+  - [39]
+  - [40]
+  - [41]
+  - [42]
+  - [43]
+  - [44]
+  - [45]
+  - [46]
+  - [47]
+  - [48]
+  - [49]
+  - [50]
+  - [51]
+  - [52]
+  - [53]
+  - [54]
+  - [55]
+  - [56]
+  - [57]
+  - [58]
+  - [59]
+  - [60]
+  - [61]
+  - [62]
+  - [63]
+  - [64]
+  - [65]
+  - [66]
+  - [67]
+  - [68]
+  - [69]
+  - [70]
+  - [71]
+  - [72]
+  - [73]
+  - [74]
+  - [75]
+  - [76]
+  - [77]
+  - [78]
+  - [79]
+  - [80]
+  - [81]
+  - [82]
+  - [83]
+  - [84]
+  - [85]
+  - [86]
+  - [87]
+  - [88]
+  - [89]
+  - [90]
+  - [91]
+  - [92]
+  - [93]
+  - [94]
+  - [95]
+  - [96]
+  - [97]
+  - [98]
+  - [99]
+  - [100]
+...
+index:select(44, {iterator = box.index.GT})
+---
+- - [45]
+  - [46]
+  - [47]
+  - [48]
+  - [49]
+  - [50]
+  - [51]
+  - [52]
+  - [53]
+  - [54]
+  - [55]
+  - [56]
+  - [57]
+  - [58]
+  - [59]
+  - [60]
+  - [61]
+  - [62]
+  - [63]
+  - [64]
+  - [65]
+  - [66]
+  - [67]
+  - [68]
+  - [69]
+  - [70]
+  - [71]
+  - [72]
+  - [73]
+  - [74]
+  - [75]
+  - [76]
+  - [77]
+  - [78]
+  - [79]
+  - [80]
+  - [81]
+  - [82]
+  - [83]
+  - [84]
+  - [85]
+  - [86]
+  - [87]
+  - [88]
+  - [89]
+  - [90]
+  - [91]
+  - [92]
+  - [93]
+  - [94]
+  - [95]
+  - [96]
+  - [97]
+  - [98]
+  - [99]
+  - [100]
+...
+index:select({}, {iterator = box.index.LE})
+---
+- - [100]
+  - [99]
+  - [98]
+  - [97]
+  - [96]
+  - [95]
+  - [94]
+  - [93]
+  - [92]
+  - [91]
+  - [90]
+  - [89]
+  - [88]
+  - [87]
+  - [86]
+  - [85]
+  - [84]
+  - [83]
+  - [82]
+  - [81]
+  - [80]
+  - [79]
+  - [78]
+  - [77]
+  - [76]
+  - [75]
+  - [74]
+  - [73]
+  - [72]
+  - [71]
+  - [70]
+  - [69]
+  - [68]
+  - [67]
+  - [66]
+  - [65]
+  - [64]
+  - [63]
+  - [62]
+  - [61]
+  - [60]
+  - [59]
+  - [58]
+  - [57]
+  - [56]
+  - [55]
+  - [54]
+  - [53]
+  - [52]
+  - [51]
+  - [50]
+  - [49]
+  - [48]
+  - [47]
+  - [46]
+  - [45]
+  - [44]
+  - [43]
+  - [42]
+  - [41]
+  - [40]
+  - [39]
+  - [38]
+  - [37]
+  - [36]
+  - [35]
+  - [34]
+  - [33]
+  - [32]
+  - [31]
+  - [30]
+  - [29]
+  - [28]
+  - [27]
+  - [26]
+  - [25]
+  - [24]
+  - [23]
+  - [22]
+  - [21]
+  - [20]
+  - [19]
+  - [18]
+  - [17]
+  - [16]
+  - [15]
+  - [14]
+  - [13]
+  - [12]
+  - [11]
+  - [10]
+  - [9]
+  - [8]
+  - [7]
+  - [6]
+  - [5]
+  - [4]
+  - [3]
+  - [2]
+  - [1]
+...
+index:select(77, {iterator = box.index.LE})
+---
+- - [77]
+  - [76]
+  - [75]
+  - [74]
+  - [73]
+  - [72]
+  - [71]
+  - [70]
+  - [69]
+  - [68]
+  - [67]
+  - [66]
+  - [65]
+  - [64]
+  - [63]
+  - [62]
+  - [61]
+  - [60]
+  - [59]
+  - [58]
+  - [57]
+  - [56]
+  - [55]
+  - [54]
+  - [53]
+  - [52]
+  - [51]
+  - [50]
+  - [49]
+  - [48]
+  - [47]
+  - [46]
+  - [45]
+  - [44]
+  - [43]
+  - [42]
+  - [41]
+  - [40]
+  - [39]
+  - [38]
+  - [37]
+  - [36]
+  - [35]
+  - [34]
+  - [33]
+  - [32]
+  - [31]
+  - [30]
+  - [29]
+  - [28]
+  - [27]
+  - [26]
+  - [25]
+  - [24]
+  - [23]
+  - [22]
+  - [21]
+  - [20]
+  - [19]
+  - [18]
+  - [17]
+  - [16]
+  - [15]
+  - [14]
+  - [13]
+  - [12]
+  - [11]
+  - [10]
+  - [9]
+  - [8]
+  - [7]
+  - [6]
+  - [5]
+  - [4]
+  - [3]
+  - [2]
+  - [1]
+...
+index:select({}, {iterator = box.index.LT})
+---
+- - [100]
+  - [99]
+  - [98]
+  - [97]
+  - [96]
+  - [95]
+  - [94]
+  - [93]
+  - [92]
+  - [91]
+  - [90]
+  - [89]
+  - [88]
+  - [87]
+  - [86]
+  - [85]
+  - [84]
+  - [83]
+  - [82]
+  - [81]
+  - [80]
+  - [79]
+  - [78]
+  - [77]
+  - [76]
+  - [75]
+  - [74]
+  - [73]
+  - [72]
+  - [71]
+  - [70]
+  - [69]
+  - [68]
+  - [67]
+  - [66]
+  - [65]
+  - [64]
+  - [63]
+  - [62]
+  - [61]
+  - [60]
+  - [59]
+  - [58]
+  - [57]
+  - [56]
+  - [55]
+  - [54]
+  - [53]
+  - [52]
+  - [51]
+  - [50]
+  - [49]
+  - [48]
+  - [47]
+  - [46]
+  - [45]
+  - [44]
+  - [43]
+  - [42]
+  - [41]
+  - [40]
+  - [39]
+  - [38]
+  - [37]
+  - [36]
+  - [35]
+  - [34]
+  - [33]
+  - [32]
+  - [31]
+  - [30]
+  - [29]
+  - [28]
+  - [27]
+  - [26]
+  - [25]
+  - [24]
+  - [23]
+  - [22]
+  - [21]
+  - [20]
+  - [19]
+  - [18]
+  - [17]
+  - [16]
+  - [15]
+  - [14]
+  - [13]
+  - [12]
+  - [11]
+  - [10]
+  - [9]
+  - [8]
+  - [7]
+  - [6]
+  - [5]
+  - [4]
+  - [3]
+  - [2]
+  - [1]
+...
+index:select(77, {iterator = box.index.LT})
+---
+- - [76]
+  - [75]
+  - [74]
+  - [73]
+  - [72]
+  - [71]
+  - [70]
+  - [69]
+  - [68]
+  - [67]
+  - [66]
+  - [65]
+  - [64]
+  - [63]
+  - [62]
+  - [61]
+  - [60]
+  - [59]
+  - [58]
+  - [57]
+  - [56]
+  - [55]
+  - [54]
+  - [53]
+  - [52]
+  - [51]
+  - [50]
+  - [49]
+  - [48]
+  - [47]
+  - [46]
+  - [45]
+  - [44]
+  - [43]
+  - [42]
+  - [41]
+  - [40]
+  - [39]
+  - [38]
+  - [37]
+  - [36]
+  - [35]
+  - [34]
+  - [33]
+  - [32]
+  - [31]
+  - [30]
+  - [29]
+  - [28]
+  - [27]
+  - [26]
+  - [25]
+  - [24]
+  - [23]
+  - [22]
+  - [21]
+  - [20]
+  - [19]
+  - [18]
+  - [17]
+  - [16]
+  - [15]
+  - [14]
+  - [13]
+  - [12]
+  - [11]
+  - [10]
+  - [9]
+  - [8]
+  - [7]
+  - [6]
+  - [5]
+  - [4]
+  - [3]
+  - [2]
+  - [1]
+...
+space:drop()
+---
+...
+-- select multi-part (num, num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num', 2, 'num'} })
+---
+...
+for key = 1, 100 do space:replace({key, key}) end
+---
+...
+index:select({}, {iterator = box.index.ALL})
+---
+- - [1, 1]
+  - [2, 2]
+  - [3, 3]
+  - [4, 4]
+  - [5, 5]
+  - [6, 6]
+  - [7, 7]
+  - [8, 8]
+  - [9, 9]
+  - [10, 10]
+  - [11, 11]
+  - [12, 12]
+  - [13, 13]
+  - [14, 14]
+  - [15, 15]
+  - [16, 16]
+  - [17, 17]
+  - [18, 18]
+  - [19, 19]
+  - [20, 20]
+  - [21, 21]
+  - [22, 22]
+  - [23, 23]
+  - [24, 24]
+  - [25, 25]
+  - [26, 26]
+  - [27, 27]
+  - [28, 28]
+  - [29, 29]
+  - [30, 30]
+  - [31, 31]
+  - [32, 32]
+  - [33, 33]
+  - [34, 34]
+  - [35, 35]
+  - [36, 36]
+  - [37, 37]
+  - [38, 38]
+  - [39, 39]
+  - [40, 40]
+  - [41, 41]
+  - [42, 42]
+  - [43, 43]
+  - [44, 44]
+  - [45, 45]
+  - [46, 46]
+  - [47, 47]
+  - [48, 48]
+  - [49, 49]
+  - [50, 50]
+  - [51, 51]
+  - [52, 52]
+  - [53, 53]
+  - [54, 54]
+  - [55, 55]
+  - [56, 56]
+  - [57, 57]
+  - [58, 58]
+  - [59, 59]
+  - [60, 60]
+  - [61, 61]
+  - [62, 62]
+  - [63, 63]
+  - [64, 64]
+  - [65, 65]
+  - [66, 66]
+  - [67, 67]
+  - [68, 68]
+  - [69, 69]
+  - [70, 70]
+  - [71, 71]
+  - [72, 72]
+  - [73, 73]
+  - [74, 74]
+  - [75, 75]
+  - [76, 76]
+  - [77, 77]
+  - [78, 78]
+  - [79, 79]
+  - [80, 80]
+  - [81, 81]
+  - [82, 82]
+  - [83, 83]
+  - [84, 84]
+  - [85, 85]
+  - [86, 86]
+  - [87, 87]
+  - [88, 88]
+  - [89, 89]
+  - [90, 90]
+  - [91, 91]
+  - [92, 92]
+  - [93, 93]
+  - [94, 94]
+  - [95, 95]
+  - [96, 96]
+  - [97, 97]
+  - [98, 98]
+  - [99, 99]
+  - [100, 100]
+...
+index:select({}, {iterator = box.index.GE})
+---
+- - [1, 1]
+  - [2, 2]
+  - [3, 3]
+  - [4, 4]
+  - [5, 5]
+  - [6, 6]
+  - [7, 7]
+  - [8, 8]
+  - [9, 9]
+  - [10, 10]
+  - [11, 11]
+  - [12, 12]
+  - [13, 13]
+  - [14, 14]
+  - [15, 15]
+  - [16, 16]
+  - [17, 17]
+  - [18, 18]
+  - [19, 19]
+  - [20, 20]
+  - [21, 21]
+  - [22, 22]
+  - [23, 23]
+  - [24, 24]
+  - [25, 25]
+  - [26, 26]
+  - [27, 27]
+  - [28, 28]
+  - [29, 29]
+  - [30, 30]
+  - [31, 31]
+  - [32, 32]
+  - [33, 33]
+  - [34, 34]
+  - [35, 35]
+  - [36, 36]
+  - [37, 37]
+  - [38, 38]
+  - [39, 39]
+  - [40, 40]
+  - [41, 41]
+  - [42, 42]
+  - [43, 43]
+  - [44, 44]
+  - [45, 45]
+  - [46, 46]
+  - [47, 47]
+  - [48, 48]
+  - [49, 49]
+  - [50, 50]
+  - [51, 51]
+  - [52, 52]
+  - [53, 53]
+  - [54, 54]
+  - [55, 55]
+  - [56, 56]
+  - [57, 57]
+  - [58, 58]
+  - [59, 59]
+  - [60, 60]
+  - [61, 61]
+  - [62, 62]
+  - [63, 63]
+  - [64, 64]
+  - [65, 65]
+  - [66, 66]
+  - [67, 67]
+  - [68, 68]
+  - [69, 69]
+  - [70, 70]
+  - [71, 71]
+  - [72, 72]
+  - [73, 73]
+  - [74, 74]
+  - [75, 75]
+  - [76, 76]
+  - [77, 77]
+  - [78, 78]
+  - [79, 79]
+  - [80, 80]
+  - [81, 81]
+  - [82, 82]
+  - [83, 83]
+  - [84, 84]
+  - [85, 85]
+  - [86, 86]
+  - [87, 87]
+  - [88, 88]
+  - [89, 89]
+  - [90, 90]
+  - [91, 91]
+  - [92, 92]
+  - [93, 93]
+  - [94, 94]
+  - [95, 95]
+  - [96, 96]
+  - [97, 97]
+  - [98, 98]
+  - [99, 99]
+  - [100, 100]
+...
+index:select({44, 44}, {iterator = box.index.GE})
+---
+- - [44, 44]
+  - [45, 45]
+  - [46, 46]
+  - [47, 47]
+  - [48, 48]
+  - [49, 49]
+  - [50, 50]
+  - [51, 51]
+  - [52, 52]
+  - [53, 53]
+  - [54, 54]
+  - [55, 55]
+  - [56, 56]
+  - [57, 57]
+  - [58, 58]
+  - [59, 59]
+  - [60, 60]
+  - [61, 61]
+  - [62, 62]
+  - [63, 63]
+  - [64, 64]
+  - [65, 65]
+  - [66, 66]
+  - [67, 67]
+  - [68, 68]
+  - [69, 69]
+  - [70, 70]
+  - [71, 71]
+  - [72, 72]
+  - [73, 73]
+  - [74, 74]
+  - [75, 75]
+  - [76, 76]
+  - [77, 77]
+  - [78, 78]
+  - [79, 79]
+  - [80, 80]
+  - [81, 81]
+  - [82, 82]
+  - [83, 83]
+  - [84, 84]
+  - [85, 85]
+  - [86, 86]
+  - [87, 87]
+  - [88, 88]
+  - [89, 89]
+  - [90, 90]
+  - [91, 91]
+  - [92, 92]
+  - [93, 93]
+  - [94, 94]
+  - [95, 95]
+  - [96, 96]
+  - [97, 97]
+  - [98, 98]
+  - [99, 99]
+  - [100, 100]
+...
+index:select({}, {iterator = box.index.GT})
+---
+- - [1, 1]
+  - [2, 2]
+  - [3, 3]
+  - [4, 4]
+  - [5, 5]
+  - [6, 6]
+  - [7, 7]
+  - [8, 8]
+  - [9, 9]
+  - [10, 10]
+  - [11, 11]
+  - [12, 12]
+  - [13, 13]
+  - [14, 14]
+  - [15, 15]
+  - [16, 16]
+  - [17, 17]
+  - [18, 18]
+  - [19, 19]
+  - [20, 20]
+  - [21, 21]
+  - [22, 22]
+  - [23, 23]
+  - [24, 24]
+  - [25, 25]
+  - [26, 26]
+  - [27, 27]
+  - [28, 28]
+  - [29, 29]
+  - [30, 30]
+  - [31, 31]
+  - [32, 32]
+  - [33, 33]
+  - [34, 34]
+  - [35, 35]
+  - [36, 36]
+  - [37, 37]
+  - [38, 38]
+  - [39, 39]
+  - [40, 40]
+  - [41, 41]
+  - [42, 42]
+  - [43, 43]
+  - [44, 44]
+  - [45, 45]
+  - [46, 46]
+  - [47, 47]
+  - [48, 48]
+  - [49, 49]
+  - [50, 50]
+  - [51, 51]
+  - [52, 52]
+  - [53, 53]
+  - [54, 54]
+  - [55, 55]
+  - [56, 56]
+  - [57, 57]
+  - [58, 58]
+  - [59, 59]
+  - [60, 60]
+  - [61, 61]
+  - [62, 62]
+  - [63, 63]
+  - [64, 64]
+  - [65, 65]
+  - [66, 66]
+  - [67, 67]
+  - [68, 68]
+  - [69, 69]
+  - [70, 70]
+  - [71, 71]
+  - [72, 72]
+  - [73, 73]
+  - [74, 74]
+  - [75, 75]
+  - [76, 76]
+  - [77, 77]
+  - [78, 78]
+  - [79, 79]
+  - [80, 80]
+  - [81, 81]
+  - [82, 82]
+  - [83, 83]
+  - [84, 84]
+  - [85, 85]
+  - [86, 86]
+  - [87, 87]
+  - [88, 88]
+  - [89, 89]
+  - [90, 90]
+  - [91, 91]
+  - [92, 92]
+  - [93, 93]
+  - [94, 94]
+  - [95, 95]
+  - [96, 96]
+  - [97, 97]
+  - [98, 98]
+  - [99, 99]
+  - [100, 100]
+...
+index:select({44, 44}, {iterator = box.index.GT})
+---
+- - [45, 45]
+  - [46, 46]
+  - [47, 47]
+  - [48, 48]
+  - [49, 49]
+  - [50, 50]
+  - [51, 51]
+  - [52, 52]
+  - [53, 53]
+  - [54, 54]
+  - [55, 55]
+  - [56, 56]
+  - [57, 57]
+  - [58, 58]
+  - [59, 59]
+  - [60, 60]
+  - [61, 61]
+  - [62, 62]
+  - [63, 63]
+  - [64, 64]
+  - [65, 65]
+  - [66, 66]
+  - [67, 67]
+  - [68, 68]
+  - [69, 69]
+  - [70, 70]
+  - [71, 71]
+  - [72, 72]
+  - [73, 73]
+  - [74, 74]
+  - [75, 75]
+  - [76, 76]
+  - [77, 77]
+  - [78, 78]
+  - [79, 79]
+  - [80, 80]
+  - [81, 81]
+  - [82, 82]
+  - [83, 83]
+  - [84, 84]
+  - [85, 85]
+  - [86, 86]
+  - [87, 87]
+  - [88, 88]
+  - [89, 89]
+  - [90, 90]
+  - [91, 91]
+  - [92, 92]
+  - [93, 93]
+  - [94, 94]
+  - [95, 95]
+  - [96, 96]
+  - [97, 97]
+  - [98, 98]
+  - [99, 99]
+  - [100, 100]
+...
+index:select({}, {iterator = box.index.LE})
+---
+- - [100, 100]
+  - [99, 99]
+  - [98, 98]
+  - [97, 97]
+  - [96, 96]
+  - [95, 95]
+  - [94, 94]
+  - [93, 93]
+  - [92, 92]
+  - [91, 91]
+  - [90, 90]
+  - [89, 89]
+  - [88, 88]
+  - [87, 87]
+  - [86, 86]
+  - [85, 85]
+  - [84, 84]
+  - [83, 83]
+  - [82, 82]
+  - [81, 81]
+  - [80, 80]
+  - [79, 79]
+  - [78, 78]
+  - [77, 77]
+  - [76, 76]
+  - [75, 75]
+  - [74, 74]
+  - [73, 73]
+  - [72, 72]
+  - [71, 71]
+  - [70, 70]
+  - [69, 69]
+  - [68, 68]
+  - [67, 67]
+  - [66, 66]
+  - [65, 65]
+  - [64, 64]
+  - [63, 63]
+  - [62, 62]
+  - [61, 61]
+  - [60, 60]
+  - [59, 59]
+  - [58, 58]
+  - [57, 57]
+  - [56, 56]
+  - [55, 55]
+  - [54, 54]
+  - [53, 53]
+  - [52, 52]
+  - [51, 51]
+  - [50, 50]
+  - [49, 49]
+  - [48, 48]
+  - [47, 47]
+  - [46, 46]
+  - [45, 45]
+  - [44, 44]
+  - [43, 43]
+  - [42, 42]
+  - [41, 41]
+  - [40, 40]
+  - [39, 39]
+  - [38, 38]
+  - [37, 37]
+  - [36, 36]
+  - [35, 35]
+  - [34, 34]
+  - [33, 33]
+  - [32, 32]
+  - [31, 31]
+  - [30, 30]
+  - [29, 29]
+  - [28, 28]
+  - [27, 27]
+  - [26, 26]
+  - [25, 25]
+  - [24, 24]
+  - [23, 23]
+  - [22, 22]
+  - [21, 21]
+  - [20, 20]
+  - [19, 19]
+  - [18, 18]
+  - [17, 17]
+  - [16, 16]
+  - [15, 15]
+  - [14, 14]
+  - [13, 13]
+  - [12, 12]
+  - [11, 11]
+  - [10, 10]
+  - [9, 9]
+  - [8, 8]
+  - [7, 7]
+  - [6, 6]
+  - [5, 5]
+  - [4, 4]
+  - [3, 3]
+  - [2, 2]
+  - [1, 1]
+...
+index:select({77, 77}, {iterator = box.index.LE})
+---
+- - [77, 77]
+  - [76, 76]
+  - [75, 75]
+  - [74, 74]
+  - [73, 73]
+  - [72, 72]
+  - [71, 71]
+  - [70, 70]
+  - [69, 69]
+  - [68, 68]
+  - [67, 67]
+  - [66, 66]
+  - [65, 65]
+  - [64, 64]
+  - [63, 63]
+  - [62, 62]
+  - [61, 61]
+  - [60, 60]
+  - [59, 59]
+  - [58, 58]
+  - [57, 57]
+  - [56, 56]
+  - [55, 55]
+  - [54, 54]
+  - [53, 53]
+  - [52, 52]
+  - [51, 51]
+  - [50, 50]
+  - [49, 49]
+  - [48, 48]
+  - [47, 47]
+  - [46, 46]
+  - [45, 45]
+  - [44, 44]
+  - [43, 43]
+  - [42, 42]
+  - [41, 41]
+  - [40, 40]
+  - [39, 39]
+  - [38, 38]
+  - [37, 37]
+  - [36, 36]
+  - [35, 35]
+  - [34, 34]
+  - [33, 33]
+  - [32, 32]
+  - [31, 31]
+  - [30, 30]
+  - [29, 29]
+  - [28, 28]
+  - [27, 27]
+  - [26, 26]
+  - [25, 25]
+  - [24, 24]
+  - [23, 23]
+  - [22, 22]
+  - [21, 21]
+  - [20, 20]
+  - [19, 19]
+  - [18, 18]
+  - [17, 17]
+  - [16, 16]
+  - [15, 15]
+  - [14, 14]
+  - [13, 13]
+  - [12, 12]
+  - [11, 11]
+  - [10, 10]
+  - [9, 9]
+  - [8, 8]
+  - [7, 7]
+  - [6, 6]
+  - [5, 5]
+  - [4, 4]
+  - [3, 3]
+  - [2, 2]
+  - [1, 1]
+...
+index:select({}, {iterator = box.index.LT})
+---
+- - [100, 100]
+  - [99, 99]
+  - [98, 98]
+  - [97, 97]
+  - [96, 96]
+  - [95, 95]
+  - [94, 94]
+  - [93, 93]
+  - [92, 92]
+  - [91, 91]
+  - [90, 90]
+  - [89, 89]
+  - [88, 88]
+  - [87, 87]
+  - [86, 86]
+  - [85, 85]
+  - [84, 84]
+  - [83, 83]
+  - [82, 82]
+  - [81, 81]
+  - [80, 80]
+  - [79, 79]
+  - [78, 78]
+  - [77, 77]
+  - [76, 76]
+  - [75, 75]
+  - [74, 74]
+  - [73, 73]
+  - [72, 72]
+  - [71, 71]
+  - [70, 70]
+  - [69, 69]
+  - [68, 68]
+  - [67, 67]
+  - [66, 66]
+  - [65, 65]
+  - [64, 64]
+  - [63, 63]
+  - [62, 62]
+  - [61, 61]
+  - [60, 60]
+  - [59, 59]
+  - [58, 58]
+  - [57, 57]
+  - [56, 56]
+  - [55, 55]
+  - [54, 54]
+  - [53, 53]
+  - [52, 52]
+  - [51, 51]
+  - [50, 50]
+  - [49, 49]
+  - [48, 48]
+  - [47, 47]
+  - [46, 46]
+  - [45, 45]
+  - [44, 44]
+  - [43, 43]
+  - [42, 42]
+  - [41, 41]
+  - [40, 40]
+  - [39, 39]
+  - [38, 38]
+  - [37, 37]
+  - [36, 36]
+  - [35, 35]
+  - [34, 34]
+  - [33, 33]
+  - [32, 32]
+  - [31, 31]
+  - [30, 30]
+  - [29, 29]
+  - [28, 28]
+  - [27, 27]
+  - [26, 26]
+  - [25, 25]
+  - [24, 24]
+  - [23, 23]
+  - [22, 22]
+  - [21, 21]
+  - [20, 20]
+  - [19, 19]
+  - [18, 18]
+  - [17, 17]
+  - [16, 16]
+  - [15, 15]
+  - [14, 14]
+  - [13, 13]
+  - [12, 12]
+  - [11, 11]
+  - [10, 10]
+  - [9, 9]
+  - [8, 8]
+  - [7, 7]
+  - [6, 6]
+  - [5, 5]
+  - [4, 4]
+  - [3, 3]
+  - [2, 2]
+  - [1, 1]
+...
+index:select({77, 77}, {iterator = box.index.LT})
+---
+- - [76, 76]
+  - [75, 75]
+  - [74, 74]
+  - [73, 73]
+  - [72, 72]
+  - [71, 71]
+  - [70, 70]
+  - [69, 69]
+  - [68, 68]
+  - [67, 67]
+  - [66, 66]
+  - [65, 65]
+  - [64, 64]
+  - [63, 63]
+  - [62, 62]
+  - [61, 61]
+  - [60, 60]
+  - [59, 59]
+  - [58, 58]
+  - [57, 57]
+  - [56, 56]
+  - [55, 55]
+  - [54, 54]
+  - [53, 53]
+  - [52, 52]
+  - [51, 51]
+  - [50, 50]
+  - [49, 49]
+  - [48, 48]
+  - [47, 47]
+  - [46, 46]
+  - [45, 45]
+  - [44, 44]
+  - [43, 43]
+  - [42, 42]
+  - [41, 41]
+  - [40, 40]
+  - [39, 39]
+  - [38, 38]
+  - [37, 37]
+  - [36, 36]
+  - [35, 35]
+  - [34, 34]
+  - [33, 33]
+  - [32, 32]
+  - [31, 31]
+  - [30, 30]
+  - [29, 29]
+  - [28, 28]
+  - [27, 27]
+  - [26, 26]
+  - [25, 25]
+  - [24, 24]
+  - [23, 23]
+  - [22, 22]
+  - [21, 21]
+  - [20, 20]
+  - [19, 19]
+  - [18, 18]
+  - [17, 17]
+  - [16, 16]
+  - [15, 15]
+  - [14, 14]
+  - [13, 13]
+  - [12, 12]
+  - [11, 11]
+  - [10, 10]
+  - [9, 9]
+  - [8, 8]
+  - [7, 7]
+  - [6, 6]
+  - [5, 5]
+  - [4, 4]
+  - [3, 3]
+  - [2, 2]
+  - [1, 1]
+...
+space:drop()
+---
+...
diff --git a/test/sophia/select.test.lua b/test/sophia/select.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..0008f37a9e1e5e62c229178f492649fa86f8b820
--- /dev/null
+++ b/test/sophia/select.test.lua
@@ -0,0 +1,47 @@
+
+-- select (str)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'str'} })
+for key = 1, 100 do space:replace({tostring(key)}) end
+index:select({}, {iterator = box.index.ALL})
+index:select({}, {iterator = box.index.GE})
+index:select(tostring(44), {iterator = box.index.GE})
+index:select({}, {iterator = box.index.GT})
+index:select(tostring(44), {iterator = box.index.GT})
+index:select({}, {iterator = box.index.LE})
+index:select(tostring(77), {iterator = box.index.LE})
+index:select({}, {iterator = box.index.LT})
+index:select(tostring(77), {iterator = box.index.LT})
+space:drop()
+
+
+-- select (num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
+for key = 1, 100 do space:replace({key}) end
+index:select({}, {iterator = box.index.ALL})
+index:select({}, {iterator = box.index.GE})
+index:select(44, {iterator = box.index.GE})
+index:select({}, {iterator = box.index.GT})
+index:select(44, {iterator = box.index.GT})
+index:select({}, {iterator = box.index.LE})
+index:select(77, {iterator = box.index.LE})
+index:select({}, {iterator = box.index.LT})
+index:select(77, {iterator = box.index.LT})
+space:drop()
+
+
+-- select multi-part (num, num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num', 2, 'num'} })
+for key = 1, 100 do space:replace({key, key}) end
+index:select({}, {iterator = box.index.ALL})
+index:select({}, {iterator = box.index.GE})
+index:select({44, 44}, {iterator = box.index.GE})
+index:select({}, {iterator = box.index.GT})
+index:select({44, 44}, {iterator = box.index.GT})
+index:select({}, {iterator = box.index.LE})
+index:select({77, 77}, {iterator = box.index.LE})
+index:select({}, {iterator = box.index.LT})
+index:select({77, 77}, {iterator = box.index.LT})
+space:drop()
diff --git a/test/sophia/snapshot_gc.result b/test/sophia/snapshot_gc.result
deleted file mode 100644
index a36c6ce6683d2d6a35d2b25d1c508b20acf4d601..0000000000000000000000000000000000000000
--- a/test/sophia/snapshot_gc.result
+++ /dev/null
@@ -1,116 +0,0 @@
-name = string.match(arg[0], "([^,]+)%.lua")
----
-...
-os.execute("rm -f " .. name .."/*.snap")
----
-- 0
-...
-os.execute("rm -f " .. name .."/*.xlog")
----
-- 0
-...
-os.execute("touch " .. name .."/mt")
----
-- 0
-...
---# stop server default
---# start server default
-space = box.schema.create_space('test', { engine = 'sophia' })
----
-...
-index = space:create_index('primary')
----
-...
-for key = 1, 351 do space:insert({key}) end
----
-...
-box.snapshot()
----
-- ok
-...
-space:drop()
----
-...
-sophia_schedule()
----
-...
-sophia_dir()[1]
----
-- 1
-...
--- ensure that previous space has been garbage collected
-space = box.schema.create_space('test', { engine = 'sophia' })
----
-...
-index = space:create_index('primary')
----
-...
-for key = 1, 351 do space:insert({key}) end
----
-...
-sophia_dir()[1] -- 2
----
-- 2
-...
-box.snapshot()
----
-- ok
-...
-space:drop()
----
-...
-sophia_schedule()
----
-...
-sophia_dir()[1] -- 1
----
-- 1
-...
-space = box.schema.create_space('test', { engine = 'sophia' })
----
-...
-index = space:create_index('primary')
----
-...
-for key = 1, 351 do space:insert({key}) end
----
-...
-sophia_dir()[1] -- 2
----
-- 2
-...
-box.snapshot()
----
-- ok
-...
-space:drop()
----
-...
-sophia_schedule()
----
-...
-sophia_dir()[1] -- 1
----
-- 1
-...
-name = string.match(arg[0], "([^,]+)%.lua")
----
-...
-os.execute("rm -f " .. name .."/*.snap")
----
-- 0
-...
-os.execute("rm -f " .. name .."/*.xlog")
----
-- 0
-...
-os.execute("rm -f " .. name .."/mt")
----
-- 0
-...
-os.execute("rm -f " .. name .."/lock")
----
-- 0
-...
---# stop server default
---# start server default
diff --git a/test/sophia/snapshot_gc.test.lua b/test/sophia/snapshot_gc.test.lua
deleted file mode 100644
index dadbd288f02a40b97c5417d62aed4438a2e34e60..0000000000000000000000000000000000000000
--- a/test/sophia/snapshot_gc.test.lua
+++ /dev/null
@@ -1,44 +0,0 @@
-name = string.match(arg[0], "([^,]+)%.lua")
-os.execute("rm -f " .. name .."/*.snap")
-os.execute("rm -f " .. name .."/*.xlog")
-os.execute("touch " .. name .."/mt")
-
---# stop server default
---# start server default
-
-space = box.schema.create_space('test', { engine = 'sophia' })
-index = space:create_index('primary')
-
-for key = 1, 351 do space:insert({key}) end
-box.snapshot()
-space:drop()
-sophia_schedule()
-sophia_dir()[1]
-
--- ensure that previous space has been garbage collected
-space = box.schema.create_space('test', { engine = 'sophia' })
-index = space:create_index('primary')
-for key = 1, 351 do space:insert({key}) end
-sophia_dir()[1] -- 2
-box.snapshot()
-space:drop()
-sophia_schedule()
-sophia_dir()[1] -- 1
-
-space = box.schema.create_space('test', { engine = 'sophia' })
-index = space:create_index('primary')
-for key = 1, 351 do space:insert({key}) end
-sophia_dir()[1] -- 2
-box.snapshot()
-space:drop()
-sophia_schedule()
-sophia_dir()[1] -- 1
-
-name = string.match(arg[0], "([^,]+)%.lua")
-os.execute("rm -f " .. name .."/*.snap")
-os.execute("rm -f " .. name .."/*.xlog")
-os.execute("rm -f " .. name .."/mt")
-os.execute("rm -f " .. name .."/lock")
-
---# stop server default
---# start server default
diff --git a/test/sophia/snapshot_view.result b/test/sophia/snapshot_view.result
deleted file mode 100644
index 30a09fc4fae56efca293b2ac7a15fbca7a0aef32..0000000000000000000000000000000000000000
--- a/test/sophia/snapshot_view.result
+++ /dev/null
@@ -1,95 +0,0 @@
-name = string.match(arg[0], "([^,]+)%.lua")
----
-...
-os.execute("rm -f " .. name .."/*.snap")
----
-- 0
-...
-os.execute("rm -f " .. name .."/*.xlog")
----
-- 0
-...
-os.execute("touch " .. name .."/mt")
----
-- 0
-...
---# stop server default
---# start server default
-space = box.schema.create_space('test', { engine = 'sophia' })
----
-...
-index = space:create_index('primary')
----
-...
-for key = 1, 351 do space:insert({key}) end
----
-...
-box.snapshot()
----
-- ok
-...
-space:drop()
----
-...
-sophia_schedule()
----
-...
-name = string.match(arg[0], "([^,]+)%.lua")
----
-...
--- remove tarantool xlogs
-os.execute("rm -f " .. name .."/*.xlog")
----
-- 0
-...
-os.execute("rm -f " .. name .."/mt")
----
-- 0
-...
-os.execute("touch " .. name .."/lock")
----
-- 0
-...
-sophia_rmdir()
----
-...
---# stop server default
---# start server default
-name = string.match(arg[0], "([^,]+)%.lua")
----
-...
-space = box.space['test']
----
-...
-space:len()
----
-- 0
-...
-sophia_dir()[1]
----
-- 1
-...
-space:drop()
----
-...
-sophia_schedule()
----
-...
-sophia_dir()[1]
----
-- 1
-...
-os.execute("rm -f " .. name .."/*.snap")
----
-- 0
-...
-os.execute("rm -f " .. name .."/*.xlog")
----
-- 0
-...
-os.execute("rm -f " .. name .."/lock")
----
-- 0
-...
---# stop server default
---# start server default
diff --git a/test/sophia/box.lua b/test/sophia/sophia.lua
similarity index 70%
rename from test/sophia/box.lua
rename to test/sophia/sophia.lua
index 270190e1075624c3988b90140506008d1eb60ad9..297ed7162b1687ff9640bfa9d3e163f4856da46c 100644
--- a/test/sophia/box.lua
+++ b/test/sophia/sophia.lua
@@ -2,25 +2,21 @@
 
 require('suite')
 
-if not file_exists('./box/lock') then
+if not file_exists('./sophia/lock') then
 	sophia_rmdir()
 	sophia_mkdir()
 end
 
 local sophia = {
-	threads = 0
+	threads = 3
 }
 
-if file_exists('./box/mt') then
-	sophia.threads = 3
-end
-
 box.cfg {
     listen            = os.getenv("LISTEN"),
     slab_alloc_arena  = 0.1,
 --    pid_file          = "tarantool.pid",
     rows_per_wal      = 50,
-    sophia_dir        = "./box/sophia_test",
+    sophia_dir        = "./sophia/sophia_test",
     sophia            = sophia,
     custom_proc_title = "default"
 }
diff --git a/test/sophia/suite.ini b/test/sophia/suite.ini
index 4a727bc00c8c579955bb592dfb947d62314b6c7e..8826561444957605e7dcf771c7eed3eac2e2bb0e 100644
--- a/test/sophia/suite.ini
+++ b/test/sophia/suite.ini
@@ -1,9 +1,9 @@
 [default]
 core = tarantool
 description = sophia integration tests
-script = box.lua
-disabled = info.test.lua truncate.test.lua random.test.lua
+script = sophia.lua
+disabled = truncate.test.lua
 valgrind_disabled =
 release_disabled =
-lua_libs = suite.lua index_random_test.lua conflict.lua
+lua_libs = suite.lua conflict.lua
 use_unix_sockets = True
diff --git a/test/sophia/suite.lua b/test/sophia/suite.lua
index 10c84e3851e67f5357bc39c36ecbe29c059e113e..f3eb05b270f2f1af33556c2c6de02e0c65e0695a 100644
--- a/test/sophia/suite.lua
+++ b/test/sophia/suite.lua
@@ -2,31 +2,12 @@
 
 local os = require('os')
 
-local ffi = require('ffi')
-ffi.cdef[[
-	int sophia_schedule(void);
-]]
-
-function sophia_schedule()
-	ffi.C.sophia_schedule()
-end
-
-function sophia_dir()
-	local i = 0
-	local list = {}
-	for file in io.popen("ls -1 ./box/sophia_test"):lines() do
-		i = i + 1
-		list[i] = file
-	end
-	return {i}
-end
-
 function sophia_mkdir(dir)
-	os.execute("mkdir ./box/sophia_test")
+	os.execute("mkdir -p ./sophia/sophia_test")
 end
 
 function sophia_rmdir(dir)
-	os.execute("rm -rf ./box/sophia_test")
+	os.execute("rm -rf ./sophia/sophia_test")
 end
 
 function file_exists(name)
diff --git a/test/sophia/transaction.result b/test/sophia/transaction.result
index b8153503e6e4cebc1ef23fbe9a7d63ec539365e6..b769a812fc73d6fe9f3db832911215a4981bd370 100644
--- a/test/sophia/transaction.result
+++ b/test/sophia/transaction.result
@@ -1,3 +1,4 @@
+-- basic transaction tests
 space = box.schema.space.create('test', { engine = 'sophia' })
 ---
 ...
@@ -101,4 +102,180 @@ t
 space:drop()
 ---
 ...
---
+-- multi-space transactions
+a = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = a:create_index('primary', { type = 'tree', parts = {1, 'num'} })
+---
+...
+b = box.schema.space.create('test_tmp', { engine = 'sophia' })
+---
+...
+index = b:create_index('primary', { type = 'tree', parts = {1, 'num'} })
+---
+...
+-- begin/rollback
+box.begin()
+---
+...
+for key = 1, 10 do a:insert({key}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, a:select({key})[1]) end
+---
+...
+t
+---
+- - [1]
+  - [2]
+  - [3]
+  - [4]
+  - [5]
+  - [6]
+  - [7]
+  - [8]
+  - [9]
+  - [10]
+...
+for key = 1, 10 do b:insert({key}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, b:select({key})[1]) end
+---
+...
+t
+---
+- - [1]
+  - [2]
+  - [3]
+  - [4]
+  - [5]
+  - [6]
+  - [7]
+  - [8]
+  - [9]
+  - [10]
+...
+box.rollback()
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do assert(#a:select({key}) == 0) end
+---
+...
+t
+---
+- []
+...
+for key = 1, 10 do assert(#b:select({key}) == 0) end
+---
+...
+t
+---
+- []
+...
+-- begin/commit insert
+box.begin()
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do a:insert({key}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do b:insert({key}) end
+---
+...
+t = {}
+---
+...
+box.commit()
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, a:select({key})[1]) end
+---
+...
+t
+---
+- - [1]
+  - [2]
+  - [3]
+  - [4]
+  - [5]
+  - [6]
+  - [7]
+  - [8]
+  - [9]
+  - [10]
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, b:select({key})[1]) end
+---
+...
+t
+---
+- - [1]
+  - [2]
+  - [3]
+  - [4]
+  - [5]
+  - [6]
+  - [7]
+  - [8]
+  - [9]
+  - [10]
+...
+-- begin/commit delete
+box.begin()
+---
+...
+for key = 1, 10 do a:delete({key}) end
+---
+...
+for key = 1, 10 do b:delete({key}) end
+---
+...
+box.commit()
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do assert(#a:select({key}) == 0) end
+---
+...
+t
+---
+- []
+...
+for key = 1, 10 do assert(#b:select({key}) == 0) end
+---
+...
+t
+---
+- []
+...
+a:drop()
+---
+...
+b:drop()
+---
+...
diff --git a/test/sophia/transaction.test.lua b/test/sophia/transaction.test.lua
index 09539c06bc2957db8053ed7c452b531bafae3baa..7d894d612b7b3d64e9ccf6c7cacfcd6c26dd36e1 100644
--- a/test/sophia/transaction.test.lua
+++ b/test/sophia/transaction.test.lua
@@ -1,22 +1,18 @@
 
+-- basic transaction tests
 space = box.schema.space.create('test', { engine = 'sophia' })
 index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
-
 -- begin/rollback
-
 box.begin()
 for key = 1, 10 do space:insert({key}) end
 t = {}
 for key = 1, 10 do table.insert(t, space:select({key})[1]) end
 t
 box.rollback()
-
 t = {}
 for key = 1, 10 do assert(#space:select({key}) == 0) end
 t
-
 -- begin/commit insert
-
 box.begin()
 t = {}
 for key = 1, 10 do space:insert({key}) end
@@ -25,16 +21,60 @@ box.commit()
 t = {}
 for key = 1, 10 do table.insert(t, space:select({key})[1]) end
 t
-
 -- begin/commit delete
-
 box.begin()
 for key = 1, 10 do space:delete({key}) end
 box.commit()
-
 t = {}
 for key = 1, 10 do assert(#space:select({key}) == 0) end
 t
-
 space:drop()
---
+
+
+-- multi-space transactions
+a = box.schema.space.create('test', { engine = 'sophia' })
+index = a:create_index('primary', { type = 'tree', parts = {1, 'num'} })
+b = box.schema.space.create('test_tmp', { engine = 'sophia' })
+index = b:create_index('primary', { type = 'tree', parts = {1, 'num'} })
+-- begin/rollback
+box.begin()
+for key = 1, 10 do a:insert({key}) end
+t = {}
+for key = 1, 10 do table.insert(t, a:select({key})[1]) end
+t
+for key = 1, 10 do b:insert({key}) end
+t = {}
+for key = 1, 10 do table.insert(t, b:select({key})[1]) end
+t
+box.rollback()
+t = {}
+for key = 1, 10 do assert(#a:select({key}) == 0) end
+t
+for key = 1, 10 do assert(#b:select({key}) == 0) end
+t
+-- begin/commit insert
+box.begin()
+t = {}
+for key = 1, 10 do a:insert({key}) end
+t = {}
+for key = 1, 10 do b:insert({key}) end
+t = {}
+box.commit()
+t = {}
+for key = 1, 10 do table.insert(t, a:select({key})[1]) end
+t
+t = {}
+for key = 1, 10 do table.insert(t, b:select({key})[1]) end
+t
+-- begin/commit delete
+box.begin()
+for key = 1, 10 do a:delete({key}) end
+for key = 1, 10 do b:delete({key}) end
+box.commit()
+t = {}
+for key = 1, 10 do assert(#a:select({key}) == 0) end
+t
+for key = 1, 10 do assert(#b:select({key}) == 0) end
+t
+a:drop()
+b:drop()
diff --git a/test/sophia/transaction_multidb.result b/test/sophia/transaction_multidb.result
deleted file mode 100644
index 42cab9b94b551be306a2ed9d02570138dfee5e84..0000000000000000000000000000000000000000
--- a/test/sophia/transaction_multidb.result
+++ /dev/null
@@ -1,182 +0,0 @@
-a = box.schema.space.create('test', { engine = 'sophia' })
----
-...
-index = a:create_index('primary', { type = 'tree', parts = {1, 'num'} })
----
-...
-b = box.schema.space.create('test_tmp', { engine = 'sophia' })
----
-...
-index = b:create_index('primary', { type = 'tree', parts = {1, 'num'} })
----
-...
--- begin/rollback
-box.begin()
----
-...
-for key = 1, 10 do a:insert({key}) end
----
-...
-t = {}
----
-...
-for key = 1, 10 do table.insert(t, a:select({key})[1]) end
----
-...
-t
----
-- - [1]
-  - [2]
-  - [3]
-  - [4]
-  - [5]
-  - [6]
-  - [7]
-  - [8]
-  - [9]
-  - [10]
-...
-for key = 1, 10 do b:insert({key}) end
----
-...
-t = {}
----
-...
-for key = 1, 10 do table.insert(t, b:select({key})[1]) end
----
-...
-t
----
-- - [1]
-  - [2]
-  - [3]
-  - [4]
-  - [5]
-  - [6]
-  - [7]
-  - [8]
-  - [9]
-  - [10]
-...
-box.rollback()
----
-...
-t = {}
----
-...
-for key = 1, 10 do assert(#a:select({key}) == 0) end
----
-...
-t
----
-- []
-...
-for key = 1, 10 do assert(#b:select({key}) == 0) end
----
-...
-t
----
-- []
-...
--- begin/commit insert
-box.begin()
----
-...
-t = {}
----
-...
-for key = 1, 10 do a:insert({key}) end
----
-...
-t = {}
----
-...
-for key = 1, 10 do b:insert({key}) end
----
-...
-t = {}
----
-...
-box.commit()
----
-...
-t = {}
----
-...
-for key = 1, 10 do table.insert(t, a:select({key})[1]) end
----
-...
-t
----
-- - [1]
-  - [2]
-  - [3]
-  - [4]
-  - [5]
-  - [6]
-  - [7]
-  - [8]
-  - [9]
-  - [10]
-...
-t = {}
----
-...
-for key = 1, 10 do table.insert(t, b:select({key})[1]) end
----
-...
-t
----
-- - [1]
-  - [2]
-  - [3]
-  - [4]
-  - [5]
-  - [6]
-  - [7]
-  - [8]
-  - [9]
-  - [10]
-...
--- begin/commit delete
-box.begin()
----
-...
-for key = 1, 10 do a:delete({key}) end
----
-...
-for key = 1, 10 do b:delete({key}) end
----
-...
-box.commit()
----
-...
-t = {}
----
-...
-for key = 1, 10 do assert(#a:select({key}) == 0) end
----
-...
-t
----
-- []
-...
-for key = 1, 10 do assert(#b:select({key}) == 0) end
----
-...
-t
----
-- []
-...
-a:drop()
----
-...
-sophia_schedule()
----
-...
-b:drop()
----
-...
-sophia_schedule()
----
-...
diff --git a/test/sophia/transaction_multidb.test.lua b/test/sophia/transaction_multidb.test.lua
deleted file mode 100644
index b4af8d62d7dc4a5f30eefebdecb1403694f43eed..0000000000000000000000000000000000000000
--- a/test/sophia/transaction_multidb.test.lua
+++ /dev/null
@@ -1,60 +0,0 @@
-
-a = box.schema.space.create('test', { engine = 'sophia' })
-index = a:create_index('primary', { type = 'tree', parts = {1, 'num'} })
-
-b = box.schema.space.create('test_tmp', { engine = 'sophia' })
-index = b:create_index('primary', { type = 'tree', parts = {1, 'num'} })
-
--- begin/rollback
-
-box.begin()
-for key = 1, 10 do a:insert({key}) end
-t = {}
-for key = 1, 10 do table.insert(t, a:select({key})[1]) end
-t
-for key = 1, 10 do b:insert({key}) end
-t = {}
-for key = 1, 10 do table.insert(t, b:select({key})[1]) end
-t
-box.rollback()
-
-t = {}
-for key = 1, 10 do assert(#a:select({key}) == 0) end
-t
-for key = 1, 10 do assert(#b:select({key}) == 0) end
-t
-
--- begin/commit insert
-
-box.begin()
-t = {}
-for key = 1, 10 do a:insert({key}) end
-t = {}
-for key = 1, 10 do b:insert({key}) end
-t = {}
-box.commit()
-
-t = {}
-for key = 1, 10 do table.insert(t, a:select({key})[1]) end
-t
-t = {}
-for key = 1, 10 do table.insert(t, b:select({key})[1]) end
-t
-
--- begin/commit delete
-
-box.begin()
-for key = 1, 10 do a:delete({key}) end
-for key = 1, 10 do b:delete({key}) end
-box.commit()
-
-t = {}
-for key = 1, 10 do assert(#a:select({key}) == 0) end
-t
-for key = 1, 10 do assert(#b:select({key}) == 0) end
-t
-
-a:drop()
-sophia_schedule()
-b:drop()
-sophia_schedule()
diff --git a/test/sophia/update.result b/test/sophia/update.result
new file mode 100644
index 0000000000000000000000000000000000000000..9ba35c3469501b3cd2d8e938779d8b9c18fbe509
--- /dev/null
+++ b/test/sophia/update.result
@@ -0,0 +1,393 @@
+-- update (str)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'str'} })
+---
+...
+for key = 1, 100 do space:replace({tostring(key)}) end
+---
+...
+for key = 1, 100 do space:update({tostring(key)}, {{'=', 2, key}}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({tostring(key)})) end
+---
+...
+t
+---
+- - ['1', 1]
+  - ['2', 2]
+  - ['3', 3]
+  - ['4', 4]
+  - ['5', 5]
+  - ['6', 6]
+  - ['7', 7]
+  - ['8', 8]
+  - ['9', 9]
+  - ['10', 10]
+  - ['11', 11]
+  - ['12', 12]
+  - ['13', 13]
+  - ['14', 14]
+  - ['15', 15]
+  - ['16', 16]
+  - ['17', 17]
+  - ['18', 18]
+  - ['19', 19]
+  - ['20', 20]
+  - ['21', 21]
+  - ['22', 22]
+  - ['23', 23]
+  - ['24', 24]
+  - ['25', 25]
+  - ['26', 26]
+  - ['27', 27]
+  - ['28', 28]
+  - ['29', 29]
+  - ['30', 30]
+  - ['31', 31]
+  - ['32', 32]
+  - ['33', 33]
+  - ['34', 34]
+  - ['35', 35]
+  - ['36', 36]
+  - ['37', 37]
+  - ['38', 38]
+  - ['39', 39]
+  - ['40', 40]
+  - ['41', 41]
+  - ['42', 42]
+  - ['43', 43]
+  - ['44', 44]
+  - ['45', 45]
+  - ['46', 46]
+  - ['47', 47]
+  - ['48', 48]
+  - ['49', 49]
+  - ['50', 50]
+  - ['51', 51]
+  - ['52', 52]
+  - ['53', 53]
+  - ['54', 54]
+  - ['55', 55]
+  - ['56', 56]
+  - ['57', 57]
+  - ['58', 58]
+  - ['59', 59]
+  - ['60', 60]
+  - ['61', 61]
+  - ['62', 62]
+  - ['63', 63]
+  - ['64', 64]
+  - ['65', 65]
+  - ['66', 66]
+  - ['67', 67]
+  - ['68', 68]
+  - ['69', 69]
+  - ['70', 70]
+  - ['71', 71]
+  - ['72', 72]
+  - ['73', 73]
+  - ['74', 74]
+  - ['75', 75]
+  - ['76', 76]
+  - ['77', 77]
+  - ['78', 78]
+  - ['79', 79]
+  - ['80', 80]
+  - ['81', 81]
+  - ['82', 82]
+  - ['83', 83]
+  - ['84', 84]
+  - ['85', 85]
+  - ['86', 86]
+  - ['87', 87]
+  - ['88', 88]
+  - ['89', 89]
+  - ['90', 90]
+  - ['91', 91]
+  - ['92', 92]
+  - ['93', 93]
+  - ['94', 94]
+  - ['95', 95]
+  - ['96', 96]
+  - ['97', 97]
+  - ['98', 98]
+  - ['99', 99]
+  - ['100', 100]
+...
+space:update({tostring(101)}, {{'=', 2, 101}})
+---
+...
+space:get({tostring(101)})
+---
+...
+space:drop()
+---
+...
+-- update (num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
+---
+...
+for key = 1, 100 do space:replace({key}) end
+---
+...
+for key = 1, 100 do space:update({key}, {{'=', 2, key}}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({key})) end
+---
+...
+t
+---
+- - [1, 1]
+  - [2, 2]
+  - [3, 3]
+  - [4, 4]
+  - [5, 5]
+  - [6, 6]
+  - [7, 7]
+  - [8, 8]
+  - [9, 9]
+  - [10, 10]
+  - [11, 11]
+  - [12, 12]
+  - [13, 13]
+  - [14, 14]
+  - [15, 15]
+  - [16, 16]
+  - [17, 17]
+  - [18, 18]
+  - [19, 19]
+  - [20, 20]
+  - [21, 21]
+  - [22, 22]
+  - [23, 23]
+  - [24, 24]
+  - [25, 25]
+  - [26, 26]
+  - [27, 27]
+  - [28, 28]
+  - [29, 29]
+  - [30, 30]
+  - [31, 31]
+  - [32, 32]
+  - [33, 33]
+  - [34, 34]
+  - [35, 35]
+  - [36, 36]
+  - [37, 37]
+  - [38, 38]
+  - [39, 39]
+  - [40, 40]
+  - [41, 41]
+  - [42, 42]
+  - [43, 43]
+  - [44, 44]
+  - [45, 45]
+  - [46, 46]
+  - [47, 47]
+  - [48, 48]
+  - [49, 49]
+  - [50, 50]
+  - [51, 51]
+  - [52, 52]
+  - [53, 53]
+  - [54, 54]
+  - [55, 55]
+  - [56, 56]
+  - [57, 57]
+  - [58, 58]
+  - [59, 59]
+  - [60, 60]
+  - [61, 61]
+  - [62, 62]
+  - [63, 63]
+  - [64, 64]
+  - [65, 65]
+  - [66, 66]
+  - [67, 67]
+  - [68, 68]
+  - [69, 69]
+  - [70, 70]
+  - [71, 71]
+  - [72, 72]
+  - [73, 73]
+  - [74, 74]
+  - [75, 75]
+  - [76, 76]
+  - [77, 77]
+  - [78, 78]
+  - [79, 79]
+  - [80, 80]
+  - [81, 81]
+  - [82, 82]
+  - [83, 83]
+  - [84, 84]
+  - [85, 85]
+  - [86, 86]
+  - [87, 87]
+  - [88, 88]
+  - [89, 89]
+  - [90, 90]
+  - [91, 91]
+  - [92, 92]
+  - [93, 93]
+  - [94, 94]
+  - [95, 95]
+  - [96, 96]
+  - [97, 97]
+  - [98, 98]
+  - [99, 99]
+  - [100, 100]
+...
+space:update({101}, {{'=', 2, 101}})
+---
+...
+space:get({101})
+---
+...
+space:drop()
+---
+...
+-- update multi-part (num, num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num', 2, 'num'} })
+---
+...
+for key = 1, 100 do space:replace({key, key}) end
+---
+...
+for key = 1, 100 do space:update({key, key}, {{'=', 3, key}}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({key, key})) end
+---
+...
+t
+---
+- - [1, 1, 1]
+  - [2, 2, 2]
+  - [3, 3, 3]
+  - [4, 4, 4]
+  - [5, 5, 5]
+  - [6, 6, 6]
+  - [7, 7, 7]
+  - [8, 8, 8]
+  - [9, 9, 9]
+  - [10, 10, 10]
+  - [11, 11, 11]
+  - [12, 12, 12]
+  - [13, 13, 13]
+  - [14, 14, 14]
+  - [15, 15, 15]
+  - [16, 16, 16]
+  - [17, 17, 17]
+  - [18, 18, 18]
+  - [19, 19, 19]
+  - [20, 20, 20]
+  - [21, 21, 21]
+  - [22, 22, 22]
+  - [23, 23, 23]
+  - [24, 24, 24]
+  - [25, 25, 25]
+  - [26, 26, 26]
+  - [27, 27, 27]
+  - [28, 28, 28]
+  - [29, 29, 29]
+  - [30, 30, 30]
+  - [31, 31, 31]
+  - [32, 32, 32]
+  - [33, 33, 33]
+  - [34, 34, 34]
+  - [35, 35, 35]
+  - [36, 36, 36]
+  - [37, 37, 37]
+  - [38, 38, 38]
+  - [39, 39, 39]
+  - [40, 40, 40]
+  - [41, 41, 41]
+  - [42, 42, 42]
+  - [43, 43, 43]
+  - [44, 44, 44]
+  - [45, 45, 45]
+  - [46, 46, 46]
+  - [47, 47, 47]
+  - [48, 48, 48]
+  - [49, 49, 49]
+  - [50, 50, 50]
+  - [51, 51, 51]
+  - [52, 52, 52]
+  - [53, 53, 53]
+  - [54, 54, 54]
+  - [55, 55, 55]
+  - [56, 56, 56]
+  - [57, 57, 57]
+  - [58, 58, 58]
+  - [59, 59, 59]
+  - [60, 60, 60]
+  - [61, 61, 61]
+  - [62, 62, 62]
+  - [63, 63, 63]
+  - [64, 64, 64]
+  - [65, 65, 65]
+  - [66, 66, 66]
+  - [67, 67, 67]
+  - [68, 68, 68]
+  - [69, 69, 69]
+  - [70, 70, 70]
+  - [71, 71, 71]
+  - [72, 72, 72]
+  - [73, 73, 73]
+  - [74, 74, 74]
+  - [75, 75, 75]
+  - [76, 76, 76]
+  - [77, 77, 77]
+  - [78, 78, 78]
+  - [79, 79, 79]
+  - [80, 80, 80]
+  - [81, 81, 81]
+  - [82, 82, 82]
+  - [83, 83, 83]
+  - [84, 84, 84]
+  - [85, 85, 85]
+  - [86, 86, 86]
+  - [87, 87, 87]
+  - [88, 88, 88]
+  - [89, 89, 89]
+  - [90, 90, 90]
+  - [91, 91, 91]
+  - [92, 92, 92]
+  - [93, 93, 93]
+  - [94, 94, 94]
+  - [95, 95, 95]
+  - [96, 96, 96]
+  - [97, 97, 97]
+  - [98, 98, 98]
+  - [99, 99, 99]
+  - [100, 100, 100]
+...
+space:update({101, 101}, {{'=', 3, 101}})
+---
+...
+space:get({101, 101})
+---
+...
+space:drop()
+---
+...
diff --git a/test/sophia/update.test.lua b/test/sophia/update.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..dc1052128a33625eab676cfe1c90ce6958f637a4
--- /dev/null
+++ b/test/sophia/update.test.lua
@@ -0,0 +1,38 @@
+
+-- update (str)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'str'} })
+for key = 1, 100 do space:replace({tostring(key)}) end
+for key = 1, 100 do space:update({tostring(key)}, {{'=', 2, key}}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({tostring(key)})) end
+t
+space:update({tostring(101)}, {{'=', 2, 101}})
+space:get({tostring(101)})
+space:drop()
+
+
+-- update (num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
+for key = 1, 100 do space:replace({key}) end
+for key = 1, 100 do space:update({key}, {{'=', 2, key}}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({key})) end
+t
+space:update({101}, {{'=', 2, 101}})
+space:get({101})
+space:drop()
+
+
+-- update multi-part (num, num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num', 2, 'num'} })
+for key = 1, 100 do space:replace({key, key}) end
+for key = 1, 100 do space:update({key, key}, {{'=', 3, key}}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({key, key})) end
+t
+space:update({101, 101}, {{'=', 3, 101}})
+space:get({101, 101})
+space:drop()
diff --git a/test/sophia/upsert.result b/test/sophia/upsert.result
new file mode 100644
index 0000000000000000000000000000000000000000..6ebe0bc1cb9545cc9de4073656075f114c0e7648
--- /dev/null
+++ b/test/sophia/upsert.result
@@ -0,0 +1,1047 @@
+-- upsert (str)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'str'} })
+---
+...
+for key = 1, 100 do space:upsert({tostring(key)}, {{'+', 2, 1}}, {tostring(key), 0}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({tostring(key)})) end
+---
+...
+t
+---
+- - ['1', 1]
+  - ['2', 1]
+  - ['3', 1]
+  - ['4', 1]
+  - ['5', 1]
+  - ['6', 1]
+  - ['7', 1]
+  - ['8', 1]
+  - ['9', 1]
+  - ['10', 1]
+  - ['11', 1]
+  - ['12', 1]
+  - ['13', 1]
+  - ['14', 1]
+  - ['15', 1]
+  - ['16', 1]
+  - ['17', 1]
+  - ['18', 1]
+  - ['19', 1]
+  - ['20', 1]
+  - ['21', 1]
+  - ['22', 1]
+  - ['23', 1]
+  - ['24', 1]
+  - ['25', 1]
+  - ['26', 1]
+  - ['27', 1]
+  - ['28', 1]
+  - ['29', 1]
+  - ['30', 1]
+  - ['31', 1]
+  - ['32', 1]
+  - ['33', 1]
+  - ['34', 1]
+  - ['35', 1]
+  - ['36', 1]
+  - ['37', 1]
+  - ['38', 1]
+  - ['39', 1]
+  - ['40', 1]
+  - ['41', 1]
+  - ['42', 1]
+  - ['43', 1]
+  - ['44', 1]
+  - ['45', 1]
+  - ['46', 1]
+  - ['47', 1]
+  - ['48', 1]
+  - ['49', 1]
+  - ['50', 1]
+  - ['51', 1]
+  - ['52', 1]
+  - ['53', 1]
+  - ['54', 1]
+  - ['55', 1]
+  - ['56', 1]
+  - ['57', 1]
+  - ['58', 1]
+  - ['59', 1]
+  - ['60', 1]
+  - ['61', 1]
+  - ['62', 1]
+  - ['63', 1]
+  - ['64', 1]
+  - ['65', 1]
+  - ['66', 1]
+  - ['67', 1]
+  - ['68', 1]
+  - ['69', 1]
+  - ['70', 1]
+  - ['71', 1]
+  - ['72', 1]
+  - ['73', 1]
+  - ['74', 1]
+  - ['75', 1]
+  - ['76', 1]
+  - ['77', 1]
+  - ['78', 1]
+  - ['79', 1]
+  - ['80', 1]
+  - ['81', 1]
+  - ['82', 1]
+  - ['83', 1]
+  - ['84', 1]
+  - ['85', 1]
+  - ['86', 1]
+  - ['87', 1]
+  - ['88', 1]
+  - ['89', 1]
+  - ['90', 1]
+  - ['91', 1]
+  - ['92', 1]
+  - ['93', 1]
+  - ['94', 1]
+  - ['95', 1]
+  - ['96', 1]
+  - ['97', 1]
+  - ['98', 1]
+  - ['99', 1]
+  - ['100', 1]
+...
+for key = 1, 100 do space:upsert({tostring(key)}, {{'+', 2, 10}}, {tostring(key), 0}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({tostring(key)})) end
+---
+...
+t
+---
+- - ['1', 11]
+  - ['2', 11]
+  - ['3', 11]
+  - ['4', 11]
+  - ['5', 11]
+  - ['6', 11]
+  - ['7', 11]
+  - ['8', 11]
+  - ['9', 11]
+  - ['10', 11]
+  - ['11', 11]
+  - ['12', 11]
+  - ['13', 11]
+  - ['14', 11]
+  - ['15', 11]
+  - ['16', 11]
+  - ['17', 11]
+  - ['18', 11]
+  - ['19', 11]
+  - ['20', 11]
+  - ['21', 11]
+  - ['22', 11]
+  - ['23', 11]
+  - ['24', 11]
+  - ['25', 11]
+  - ['26', 11]
+  - ['27', 11]
+  - ['28', 11]
+  - ['29', 11]
+  - ['30', 11]
+  - ['31', 11]
+  - ['32', 11]
+  - ['33', 11]
+  - ['34', 11]
+  - ['35', 11]
+  - ['36', 11]
+  - ['37', 11]
+  - ['38', 11]
+  - ['39', 11]
+  - ['40', 11]
+  - ['41', 11]
+  - ['42', 11]
+  - ['43', 11]
+  - ['44', 11]
+  - ['45', 11]
+  - ['46', 11]
+  - ['47', 11]
+  - ['48', 11]
+  - ['49', 11]
+  - ['50', 11]
+  - ['51', 11]
+  - ['52', 11]
+  - ['53', 11]
+  - ['54', 11]
+  - ['55', 11]
+  - ['56', 11]
+  - ['57', 11]
+  - ['58', 11]
+  - ['59', 11]
+  - ['60', 11]
+  - ['61', 11]
+  - ['62', 11]
+  - ['63', 11]
+  - ['64', 11]
+  - ['65', 11]
+  - ['66', 11]
+  - ['67', 11]
+  - ['68', 11]
+  - ['69', 11]
+  - ['70', 11]
+  - ['71', 11]
+  - ['72', 11]
+  - ['73', 11]
+  - ['74', 11]
+  - ['75', 11]
+  - ['76', 11]
+  - ['77', 11]
+  - ['78', 11]
+  - ['79', 11]
+  - ['80', 11]
+  - ['81', 11]
+  - ['82', 11]
+  - ['83', 11]
+  - ['84', 11]
+  - ['85', 11]
+  - ['86', 11]
+  - ['87', 11]
+  - ['88', 11]
+  - ['89', 11]
+  - ['90', 11]
+  - ['91', 11]
+  - ['92', 11]
+  - ['93', 11]
+  - ['94', 11]
+  - ['95', 11]
+  - ['96', 11]
+  - ['97', 11]
+  - ['98', 11]
+  - ['99', 11]
+  - ['100', 11]
+...
+for key = 1, 100 do space:delete({tostring(key)}) end
+---
+...
+for key = 1, 100 do space:upsert({tostring(key)}, {{'+', 2, 1}, {'=', 3, key}}, {tostring(key), 0}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({tostring(key)})) end
+---
+...
+t
+---
+- - ['1', 1, 1]
+  - ['2', 1, 2]
+  - ['3', 1, 3]
+  - ['4', 1, 4]
+  - ['5', 1, 5]
+  - ['6', 1, 6]
+  - ['7', 1, 7]
+  - ['8', 1, 8]
+  - ['9', 1, 9]
+  - ['10', 1, 10]
+  - ['11', 1, 11]
+  - ['12', 1, 12]
+  - ['13', 1, 13]
+  - ['14', 1, 14]
+  - ['15', 1, 15]
+  - ['16', 1, 16]
+  - ['17', 1, 17]
+  - ['18', 1, 18]
+  - ['19', 1, 19]
+  - ['20', 1, 20]
+  - ['21', 1, 21]
+  - ['22', 1, 22]
+  - ['23', 1, 23]
+  - ['24', 1, 24]
+  - ['25', 1, 25]
+  - ['26', 1, 26]
+  - ['27', 1, 27]
+  - ['28', 1, 28]
+  - ['29', 1, 29]
+  - ['30', 1, 30]
+  - ['31', 1, 31]
+  - ['32', 1, 32]
+  - ['33', 1, 33]
+  - ['34', 1, 34]
+  - ['35', 1, 35]
+  - ['36', 1, 36]
+  - ['37', 1, 37]
+  - ['38', 1, 38]
+  - ['39', 1, 39]
+  - ['40', 1, 40]
+  - ['41', 1, 41]
+  - ['42', 1, 42]
+  - ['43', 1, 43]
+  - ['44', 1, 44]
+  - ['45', 1, 45]
+  - ['46', 1, 46]
+  - ['47', 1, 47]
+  - ['48', 1, 48]
+  - ['49', 1, 49]
+  - ['50', 1, 50]
+  - ['51', 1, 51]
+  - ['52', 1, 52]
+  - ['53', 1, 53]
+  - ['54', 1, 54]
+  - ['55', 1, 55]
+  - ['56', 1, 56]
+  - ['57', 1, 57]
+  - ['58', 1, 58]
+  - ['59', 1, 59]
+  - ['60', 1, 60]
+  - ['61', 1, 61]
+  - ['62', 1, 62]
+  - ['63', 1, 63]
+  - ['64', 1, 64]
+  - ['65', 1, 65]
+  - ['66', 1, 66]
+  - ['67', 1, 67]
+  - ['68', 1, 68]
+  - ['69', 1, 69]
+  - ['70', 1, 70]
+  - ['71', 1, 71]
+  - ['72', 1, 72]
+  - ['73', 1, 73]
+  - ['74', 1, 74]
+  - ['75', 1, 75]
+  - ['76', 1, 76]
+  - ['77', 1, 77]
+  - ['78', 1, 78]
+  - ['79', 1, 79]
+  - ['80', 1, 80]
+  - ['81', 1, 81]
+  - ['82', 1, 82]
+  - ['83', 1, 83]
+  - ['84', 1, 84]
+  - ['85', 1, 85]
+  - ['86', 1, 86]
+  - ['87', 1, 87]
+  - ['88', 1, 88]
+  - ['89', 1, 89]
+  - ['90', 1, 90]
+  - ['91', 1, 91]
+  - ['92', 1, 92]
+  - ['93', 1, 93]
+  - ['94', 1, 94]
+  - ['95', 1, 95]
+  - ['96', 1, 96]
+  - ['97', 1, 97]
+  - ['98', 1, 98]
+  - ['99', 1, 99]
+  - ['100', 1, 100]
+...
+space:drop()
+---
+...
+-- upsert (num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
+---
+...
+for key = 1, 100 do space:upsert({key}, {{'+', 2, 1}}, {key, 0}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({key})) end
+---
+...
+t
+---
+- - [1, 1]
+  - [2, 1]
+  - [3, 1]
+  - [4, 1]
+  - [5, 1]
+  - [6, 1]
+  - [7, 1]
+  - [8, 1]
+  - [9, 1]
+  - [10, 1]
+  - [11, 1]
+  - [12, 1]
+  - [13, 1]
+  - [14, 1]
+  - [15, 1]
+  - [16, 1]
+  - [17, 1]
+  - [18, 1]
+  - [19, 1]
+  - [20, 1]
+  - [21, 1]
+  - [22, 1]
+  - [23, 1]
+  - [24, 1]
+  - [25, 1]
+  - [26, 1]
+  - [27, 1]
+  - [28, 1]
+  - [29, 1]
+  - [30, 1]
+  - [31, 1]
+  - [32, 1]
+  - [33, 1]
+  - [34, 1]
+  - [35, 1]
+  - [36, 1]
+  - [37, 1]
+  - [38, 1]
+  - [39, 1]
+  - [40, 1]
+  - [41, 1]
+  - [42, 1]
+  - [43, 1]
+  - [44, 1]
+  - [45, 1]
+  - [46, 1]
+  - [47, 1]
+  - [48, 1]
+  - [49, 1]
+  - [50, 1]
+  - [51, 1]
+  - [52, 1]
+  - [53, 1]
+  - [54, 1]
+  - [55, 1]
+  - [56, 1]
+  - [57, 1]
+  - [58, 1]
+  - [59, 1]
+  - [60, 1]
+  - [61, 1]
+  - [62, 1]
+  - [63, 1]
+  - [64, 1]
+  - [65, 1]
+  - [66, 1]
+  - [67, 1]
+  - [68, 1]
+  - [69, 1]
+  - [70, 1]
+  - [71, 1]
+  - [72, 1]
+  - [73, 1]
+  - [74, 1]
+  - [75, 1]
+  - [76, 1]
+  - [77, 1]
+  - [78, 1]
+  - [79, 1]
+  - [80, 1]
+  - [81, 1]
+  - [82, 1]
+  - [83, 1]
+  - [84, 1]
+  - [85, 1]
+  - [86, 1]
+  - [87, 1]
+  - [88, 1]
+  - [89, 1]
+  - [90, 1]
+  - [91, 1]
+  - [92, 1]
+  - [93, 1]
+  - [94, 1]
+  - [95, 1]
+  - [96, 1]
+  - [97, 1]
+  - [98, 1]
+  - [99, 1]
+  - [100, 1]
+...
+for key = 1, 100 do space:upsert({key}, {{'+', 2, 10}}, {key, 0}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({key})) end
+---
+...
+t
+---
+- - [1, 11]
+  - [2, 11]
+  - [3, 11]
+  - [4, 11]
+  - [5, 11]
+  - [6, 11]
+  - [7, 11]
+  - [8, 11]
+  - [9, 11]
+  - [10, 11]
+  - [11, 11]
+  - [12, 11]
+  - [13, 11]
+  - [14, 11]
+  - [15, 11]
+  - [16, 11]
+  - [17, 11]
+  - [18, 11]
+  - [19, 11]
+  - [20, 11]
+  - [21, 11]
+  - [22, 11]
+  - [23, 11]
+  - [24, 11]
+  - [25, 11]
+  - [26, 11]
+  - [27, 11]
+  - [28, 11]
+  - [29, 11]
+  - [30, 11]
+  - [31, 11]
+  - [32, 11]
+  - [33, 11]
+  - [34, 11]
+  - [35, 11]
+  - [36, 11]
+  - [37, 11]
+  - [38, 11]
+  - [39, 11]
+  - [40, 11]
+  - [41, 11]
+  - [42, 11]
+  - [43, 11]
+  - [44, 11]
+  - [45, 11]
+  - [46, 11]
+  - [47, 11]
+  - [48, 11]
+  - [49, 11]
+  - [50, 11]
+  - [51, 11]
+  - [52, 11]
+  - [53, 11]
+  - [54, 11]
+  - [55, 11]
+  - [56, 11]
+  - [57, 11]
+  - [58, 11]
+  - [59, 11]
+  - [60, 11]
+  - [61, 11]
+  - [62, 11]
+  - [63, 11]
+  - [64, 11]
+  - [65, 11]
+  - [66, 11]
+  - [67, 11]
+  - [68, 11]
+  - [69, 11]
+  - [70, 11]
+  - [71, 11]
+  - [72, 11]
+  - [73, 11]
+  - [74, 11]
+  - [75, 11]
+  - [76, 11]
+  - [77, 11]
+  - [78, 11]
+  - [79, 11]
+  - [80, 11]
+  - [81, 11]
+  - [82, 11]
+  - [83, 11]
+  - [84, 11]
+  - [85, 11]
+  - [86, 11]
+  - [87, 11]
+  - [88, 11]
+  - [89, 11]
+  - [90, 11]
+  - [91, 11]
+  - [92, 11]
+  - [93, 11]
+  - [94, 11]
+  - [95, 11]
+  - [96, 11]
+  - [97, 11]
+  - [98, 11]
+  - [99, 11]
+  - [100, 11]
+...
+for key = 1, 100 do space:delete({key}) end
+---
+...
+for key = 1, 100 do space:upsert({key}, {{'+', 2, 1}, {'=', 3, key}}, {key, 0}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({key})) end
+---
+...
+t
+---
+- - [1, 1, 1]
+  - [2, 1, 2]
+  - [3, 1, 3]
+  - [4, 1, 4]
+  - [5, 1, 5]
+  - [6, 1, 6]
+  - [7, 1, 7]
+  - [8, 1, 8]
+  - [9, 1, 9]
+  - [10, 1, 10]
+  - [11, 1, 11]
+  - [12, 1, 12]
+  - [13, 1, 13]
+  - [14, 1, 14]
+  - [15, 1, 15]
+  - [16, 1, 16]
+  - [17, 1, 17]
+  - [18, 1, 18]
+  - [19, 1, 19]
+  - [20, 1, 20]
+  - [21, 1, 21]
+  - [22, 1, 22]
+  - [23, 1, 23]
+  - [24, 1, 24]
+  - [25, 1, 25]
+  - [26, 1, 26]
+  - [27, 1, 27]
+  - [28, 1, 28]
+  - [29, 1, 29]
+  - [30, 1, 30]
+  - [31, 1, 31]
+  - [32, 1, 32]
+  - [33, 1, 33]
+  - [34, 1, 34]
+  - [35, 1, 35]
+  - [36, 1, 36]
+  - [37, 1, 37]
+  - [38, 1, 38]
+  - [39, 1, 39]
+  - [40, 1, 40]
+  - [41, 1, 41]
+  - [42, 1, 42]
+  - [43, 1, 43]
+  - [44, 1, 44]
+  - [45, 1, 45]
+  - [46, 1, 46]
+  - [47, 1, 47]
+  - [48, 1, 48]
+  - [49, 1, 49]
+  - [50, 1, 50]
+  - [51, 1, 51]
+  - [52, 1, 52]
+  - [53, 1, 53]
+  - [54, 1, 54]
+  - [55, 1, 55]
+  - [56, 1, 56]
+  - [57, 1, 57]
+  - [58, 1, 58]
+  - [59, 1, 59]
+  - [60, 1, 60]
+  - [61, 1, 61]
+  - [62, 1, 62]
+  - [63, 1, 63]
+  - [64, 1, 64]
+  - [65, 1, 65]
+  - [66, 1, 66]
+  - [67, 1, 67]
+  - [68, 1, 68]
+  - [69, 1, 69]
+  - [70, 1, 70]
+  - [71, 1, 71]
+  - [72, 1, 72]
+  - [73, 1, 73]
+  - [74, 1, 74]
+  - [75, 1, 75]
+  - [76, 1, 76]
+  - [77, 1, 77]
+  - [78, 1, 78]
+  - [79, 1, 79]
+  - [80, 1, 80]
+  - [81, 1, 81]
+  - [82, 1, 82]
+  - [83, 1, 83]
+  - [84, 1, 84]
+  - [85, 1, 85]
+  - [86, 1, 86]
+  - [87, 1, 87]
+  - [88, 1, 88]
+  - [89, 1, 89]
+  - [90, 1, 90]
+  - [91, 1, 91]
+  - [92, 1, 92]
+  - [93, 1, 93]
+  - [94, 1, 94]
+  - [95, 1, 95]
+  - [96, 1, 96]
+  - [97, 1, 97]
+  - [98, 1, 98]
+  - [99, 1, 99]
+  - [100, 1, 100]
+...
+space:drop()
+---
+...
+-- upsert multi-part (num, num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num', 2, 'num'} })
+---
+...
+for key = 1, 100 do space:upsert({key, key}, {{'+', 3, 1}}, {key, key, 0}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({key, key})) end
+---
+...
+t
+---
+- - [1, 1, 1]
+  - [2, 2, 1]
+  - [3, 3, 1]
+  - [4, 4, 1]
+  - [5, 5, 1]
+  - [6, 6, 1]
+  - [7, 7, 1]
+  - [8, 8, 1]
+  - [9, 9, 1]
+  - [10, 10, 1]
+  - [11, 11, 1]
+  - [12, 12, 1]
+  - [13, 13, 1]
+  - [14, 14, 1]
+  - [15, 15, 1]
+  - [16, 16, 1]
+  - [17, 17, 1]
+  - [18, 18, 1]
+  - [19, 19, 1]
+  - [20, 20, 1]
+  - [21, 21, 1]
+  - [22, 22, 1]
+  - [23, 23, 1]
+  - [24, 24, 1]
+  - [25, 25, 1]
+  - [26, 26, 1]
+  - [27, 27, 1]
+  - [28, 28, 1]
+  - [29, 29, 1]
+  - [30, 30, 1]
+  - [31, 31, 1]
+  - [32, 32, 1]
+  - [33, 33, 1]
+  - [34, 34, 1]
+  - [35, 35, 1]
+  - [36, 36, 1]
+  - [37, 37, 1]
+  - [38, 38, 1]
+  - [39, 39, 1]
+  - [40, 40, 1]
+  - [41, 41, 1]
+  - [42, 42, 1]
+  - [43, 43, 1]
+  - [44, 44, 1]
+  - [45, 45, 1]
+  - [46, 46, 1]
+  - [47, 47, 1]
+  - [48, 48, 1]
+  - [49, 49, 1]
+  - [50, 50, 1]
+  - [51, 51, 1]
+  - [52, 52, 1]
+  - [53, 53, 1]
+  - [54, 54, 1]
+  - [55, 55, 1]
+  - [56, 56, 1]
+  - [57, 57, 1]
+  - [58, 58, 1]
+  - [59, 59, 1]
+  - [60, 60, 1]
+  - [61, 61, 1]
+  - [62, 62, 1]
+  - [63, 63, 1]
+  - [64, 64, 1]
+  - [65, 65, 1]
+  - [66, 66, 1]
+  - [67, 67, 1]
+  - [68, 68, 1]
+  - [69, 69, 1]
+  - [70, 70, 1]
+  - [71, 71, 1]
+  - [72, 72, 1]
+  - [73, 73, 1]
+  - [74, 74, 1]
+  - [75, 75, 1]
+  - [76, 76, 1]
+  - [77, 77, 1]
+  - [78, 78, 1]
+  - [79, 79, 1]
+  - [80, 80, 1]
+  - [81, 81, 1]
+  - [82, 82, 1]
+  - [83, 83, 1]
+  - [84, 84, 1]
+  - [85, 85, 1]
+  - [86, 86, 1]
+  - [87, 87, 1]
+  - [88, 88, 1]
+  - [89, 89, 1]
+  - [90, 90, 1]
+  - [91, 91, 1]
+  - [92, 92, 1]
+  - [93, 93, 1]
+  - [94, 94, 1]
+  - [95, 95, 1]
+  - [96, 96, 1]
+  - [97, 97, 1]
+  - [98, 98, 1]
+  - [99, 99, 1]
+  - [100, 100, 1]
+...
+for key = 1, 100 do space:upsert({key, key}, {{'+', 3, 10}}, {key, key, 0}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({key, key})) end
+---
+...
+t
+---
+- - [1, 1, 11]
+  - [2, 2, 11]
+  - [3, 3, 11]
+  - [4, 4, 11]
+  - [5, 5, 11]
+  - [6, 6, 11]
+  - [7, 7, 11]
+  - [8, 8, 11]
+  - [9, 9, 11]
+  - [10, 10, 11]
+  - [11, 11, 11]
+  - [12, 12, 11]
+  - [13, 13, 11]
+  - [14, 14, 11]
+  - [15, 15, 11]
+  - [16, 16, 11]
+  - [17, 17, 11]
+  - [18, 18, 11]
+  - [19, 19, 11]
+  - [20, 20, 11]
+  - [21, 21, 11]
+  - [22, 22, 11]
+  - [23, 23, 11]
+  - [24, 24, 11]
+  - [25, 25, 11]
+  - [26, 26, 11]
+  - [27, 27, 11]
+  - [28, 28, 11]
+  - [29, 29, 11]
+  - [30, 30, 11]
+  - [31, 31, 11]
+  - [32, 32, 11]
+  - [33, 33, 11]
+  - [34, 34, 11]
+  - [35, 35, 11]
+  - [36, 36, 11]
+  - [37, 37, 11]
+  - [38, 38, 11]
+  - [39, 39, 11]
+  - [40, 40, 11]
+  - [41, 41, 11]
+  - [42, 42, 11]
+  - [43, 43, 11]
+  - [44, 44, 11]
+  - [45, 45, 11]
+  - [46, 46, 11]
+  - [47, 47, 11]
+  - [48, 48, 11]
+  - [49, 49, 11]
+  - [50, 50, 11]
+  - [51, 51, 11]
+  - [52, 52, 11]
+  - [53, 53, 11]
+  - [54, 54, 11]
+  - [55, 55, 11]
+  - [56, 56, 11]
+  - [57, 57, 11]
+  - [58, 58, 11]
+  - [59, 59, 11]
+  - [60, 60, 11]
+  - [61, 61, 11]
+  - [62, 62, 11]
+  - [63, 63, 11]
+  - [64, 64, 11]
+  - [65, 65, 11]
+  - [66, 66, 11]
+  - [67, 67, 11]
+  - [68, 68, 11]
+  - [69, 69, 11]
+  - [70, 70, 11]
+  - [71, 71, 11]
+  - [72, 72, 11]
+  - [73, 73, 11]
+  - [74, 74, 11]
+  - [75, 75, 11]
+  - [76, 76, 11]
+  - [77, 77, 11]
+  - [78, 78, 11]
+  - [79, 79, 11]
+  - [80, 80, 11]
+  - [81, 81, 11]
+  - [82, 82, 11]
+  - [83, 83, 11]
+  - [84, 84, 11]
+  - [85, 85, 11]
+  - [86, 86, 11]
+  - [87, 87, 11]
+  - [88, 88, 11]
+  - [89, 89, 11]
+  - [90, 90, 11]
+  - [91, 91, 11]
+  - [92, 92, 11]
+  - [93, 93, 11]
+  - [94, 94, 11]
+  - [95, 95, 11]
+  - [96, 96, 11]
+  - [97, 97, 11]
+  - [98, 98, 11]
+  - [99, 99, 11]
+  - [100, 100, 11]
+...
+for key = 1, 100 do space:delete({key, key}) end
+---
+...
+for key = 1, 100 do space:upsert({key, key}, {{'+', 3, 1}, {'=', 4, key}}, {key, key, 0}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 100 do table.insert(t, space:get({key, key})) end
+---
+...
+t
+---
+- - [1, 1, 1, 1]
+  - [2, 2, 1, 2]
+  - [3, 3, 1, 3]
+  - [4, 4, 1, 4]
+  - [5, 5, 1, 5]
+  - [6, 6, 1, 6]
+  - [7, 7, 1, 7]
+  - [8, 8, 1, 8]
+  - [9, 9, 1, 9]
+  - [10, 10, 1, 10]
+  - [11, 11, 1, 11]
+  - [12, 12, 1, 12]
+  - [13, 13, 1, 13]
+  - [14, 14, 1, 14]
+  - [15, 15, 1, 15]
+  - [16, 16, 1, 16]
+  - [17, 17, 1, 17]
+  - [18, 18, 1, 18]
+  - [19, 19, 1, 19]
+  - [20, 20, 1, 20]
+  - [21, 21, 1, 21]
+  - [22, 22, 1, 22]
+  - [23, 23, 1, 23]
+  - [24, 24, 1, 24]
+  - [25, 25, 1, 25]
+  - [26, 26, 1, 26]
+  - [27, 27, 1, 27]
+  - [28, 28, 1, 28]
+  - [29, 29, 1, 29]
+  - [30, 30, 1, 30]
+  - [31, 31, 1, 31]
+  - [32, 32, 1, 32]
+  - [33, 33, 1, 33]
+  - [34, 34, 1, 34]
+  - [35, 35, 1, 35]
+  - [36, 36, 1, 36]
+  - [37, 37, 1, 37]
+  - [38, 38, 1, 38]
+  - [39, 39, 1, 39]
+  - [40, 40, 1, 40]
+  - [41, 41, 1, 41]
+  - [42, 42, 1, 42]
+  - [43, 43, 1, 43]
+  - [44, 44, 1, 44]
+  - [45, 45, 1, 45]
+  - [46, 46, 1, 46]
+  - [47, 47, 1, 47]
+  - [48, 48, 1, 48]
+  - [49, 49, 1, 49]
+  - [50, 50, 1, 50]
+  - [51, 51, 1, 51]
+  - [52, 52, 1, 52]
+  - [53, 53, 1, 53]
+  - [54, 54, 1, 54]
+  - [55, 55, 1, 55]
+  - [56, 56, 1, 56]
+  - [57, 57, 1, 57]
+  - [58, 58, 1, 58]
+  - [59, 59, 1, 59]
+  - [60, 60, 1, 60]
+  - [61, 61, 1, 61]
+  - [62, 62, 1, 62]
+  - [63, 63, 1, 63]
+  - [64, 64, 1, 64]
+  - [65, 65, 1, 65]
+  - [66, 66, 1, 66]
+  - [67, 67, 1, 67]
+  - [68, 68, 1, 68]
+  - [69, 69, 1, 69]
+  - [70, 70, 1, 70]
+  - [71, 71, 1, 71]
+  - [72, 72, 1, 72]
+  - [73, 73, 1, 73]
+  - [74, 74, 1, 74]
+  - [75, 75, 1, 75]
+  - [76, 76, 1, 76]
+  - [77, 77, 1, 77]
+  - [78, 78, 1, 78]
+  - [79, 79, 1, 79]
+  - [80, 80, 1, 80]
+  - [81, 81, 1, 81]
+  - [82, 82, 1, 82]
+  - [83, 83, 1, 83]
+  - [84, 84, 1, 84]
+  - [85, 85, 1, 85]
+  - [86, 86, 1, 86]
+  - [87, 87, 1, 87]
+  - [88, 88, 1, 88]
+  - [89, 89, 1, 89]
+  - [90, 90, 1, 90]
+  - [91, 91, 1, 91]
+  - [92, 92, 1, 92]
+  - [93, 93, 1, 93]
+  - [94, 94, 1, 94]
+  - [95, 95, 1, 95]
+  - [96, 96, 1, 96]
+  - [97, 97, 1, 97]
+  - [98, 98, 1, 98]
+  - [99, 99, 1, 99]
+  - [100, 100, 1, 100]
+...
+space:drop()
+---
+...
diff --git a/test/sophia/upsert.test.lua b/test/sophia/upsert.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..0dfebf0072fd3afce0e27702fd8b2fb879ab0cd9
--- /dev/null
+++ b/test/sophia/upsert.test.lua
@@ -0,0 +1,56 @@
+
+-- upsert (str)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'str'} })
+for key = 1, 100 do space:upsert({tostring(key)}, {{'+', 2, 1}}, {tostring(key), 0}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({tostring(key)})) end
+t
+for key = 1, 100 do space:upsert({tostring(key)}, {{'+', 2, 10}}, {tostring(key), 0}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({tostring(key)})) end
+t
+for key = 1, 100 do space:delete({tostring(key)}) end
+for key = 1, 100 do space:upsert({tostring(key)}, {{'+', 2, 1}, {'=', 3, key}}, {tostring(key), 0}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({tostring(key)})) end
+t
+space:drop()
+
+
+-- upsert (num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num'} })
+for key = 1, 100 do space:upsert({key}, {{'+', 2, 1}}, {key, 0}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({key})) end
+t
+for key = 1, 100 do space:upsert({key}, {{'+', 2, 10}}, {key, 0}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({key})) end
+t
+for key = 1, 100 do space:delete({key}) end
+for key = 1, 100 do space:upsert({key}, {{'+', 2, 1}, {'=', 3, key}}, {key, 0}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({key})) end
+t
+space:drop()
+
+
+-- upsert multi-part (num, num)
+space = box.schema.space.create('test', { engine = 'sophia' })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'num', 2, 'num'} })
+for key = 1, 100 do space:upsert({key, key}, {{'+', 3, 1}}, {key, key, 0}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({key, key})) end
+t
+for key = 1, 100 do space:upsert({key, key}, {{'+', 3, 10}}, {key, key, 0}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({key, key})) end
+t
+for key = 1, 100 do space:delete({key, key}) end
+for key = 1, 100 do space:upsert({key, key}, {{'+', 3, 1}, {'=', 4, key}}, {key, key, 0}) end
+t = {}
+for key = 1, 100 do table.insert(t, space:get({key, key})) end
+t
+space:drop()
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index f76c65b22a0048e625455f1e683c2f1b1f98e9df..c86a062ad7d52fce7758d5b0d33995a342e1032e 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -60,7 +60,8 @@ target_link_libraries(light.test small)
 add_executable(vclock.test vclock.cc unit.c
     ${CMAKE_SOURCE_DIR}/src/box/vclock.c
     ${CMAKE_SOURCE_DIR}/src/box/errcode.c
-    ${CMAKE_SOURCE_DIR}/src/box/error.cc)
+    ${CMAKE_SOURCE_DIR}/src/box/error.cc
+    ${CMAKE_SOURCE_DIR}/src/rmean.cc)
 target_link_libraries(vclock.test core small)
 add_executable(quota.test quota.cc unit.c)
 target_link_libraries(quota.test pthread)
@@ -117,3 +118,7 @@ add_executable(reflection_cxx.test reflection_cxx.cc unit.c
 add_executable(csv.test csv.c
     ${CMAKE_SOURCE_DIR}/src/lib/csv/csv.c
 )
+
+add_executable(rmean.test rmean.cc unit.c
+        ${CMAKE_SOURCE_DIR}/src/rmean.cc)
+target_link_libraries(rmean.test core)
diff --git a/test/unit/rmean.cc b/test/unit/rmean.cc
new file mode 100644
index 0000000000000000000000000000000000000000..797d12dda10aef4d266869499a747f8a37090605
--- /dev/null
+++ b/test/unit/rmean.cc
@@ -0,0 +1,63 @@
+#include "rmean.h"
+#include "memory.h"
+#include "unit.h"
+
+int print_stat(const char *name, int rps, int64_t total, void* ctx)
+{
+	printf("%s: rps %d, total %d%c", name, rps, (int)total,
+	       name[2] == '2' ? '\n' : '\t');
+	return 0;
+}
+
+void test_100rps(rmean *st)
+{
+	header();
+	printf("Send 100 requests every second for 10 seconds\n");
+	printf("Calc rps at third and last second\n");
+	for(int i = 0; i < 10; i++) { /* 10 seconds */
+		rmean_collect(st, 0, 100); /* send 100 requests */
+		rmean_timer_tick(st);
+		if (i == 2 || i == 9) /* two checks */
+			rmean_foreach(st, print_stat, NULL);
+	}
+	/* 10 seconds, 1000 in EV1, 100 rps */
+	footer();
+}
+
+void test_mean15rps(rmean *st)
+{
+	header();
+	printf("Send 15 rps on the average, and 3 rps to EV2\n");
+	for(int i = 0; i < 10; i++) { /* 10 seconds */
+		for(int j = 0; j < 15; j++) {
+			rmean_collect(st, 0, 1); /* send 15 requests */
+			if((i * 3 + 2 + j) % 15 == 0)
+				rmean_timer_tick(st);
+		}
+		rmean_collect(st, 1, 3);
+	}
+	rmean_foreach(st, print_stat, NULL);
+	/* 10 seconds, 1000 + 150 in EV1, 15 rps. 30 in EV2, 3 rps*/
+	footer();
+}
+
+int main()
+{
+	printf("Stat. 2 names, timer simulation\n");
+
+	memory_init();
+	fiber_init();
+
+	struct rmean *st;
+	const char *name[] = {"EV1", "EV2"};
+	st = rmean_new(name, 2);
+
+	test_100rps(st);
+	test_mean15rps(st);
+
+	rmean_delete(st);
+
+	fiber_free();
+	memory_free();
+	return 0;
+}
diff --git a/test/unit/rmean.result b/test/unit/rmean.result
new file mode 100644
index 0000000000000000000000000000000000000000..b881d7becdefb36ef887aae88d4bdc380595a76e
--- /dev/null
+++ b/test/unit/rmean.result
@@ -0,0 +1,12 @@
+Stat. 2 names, timer simulation
+	*** test_100rps ***
+Send 100 requests every second for 10 seconds
+Calc rps at third and last second
+EV1: rps 60, total 300	EV2: rps 0, total 0
+EV1: rps 100, total 1000	EV2: rps 0, total 0
+	*** test_100rps: done ***
+ 	*** test_mean15rps ***
+Send 15 rps on the average, and 3 rps to EV2
+EV1: rps 15, total 1150	EV2: rps 3, total 30
+	*** test_mean15rps: done ***
+ 
\ No newline at end of file
diff --git a/test/unit/rtree.cc b/test/unit/rtree.cc
index 3469fe2d1ec705936d0e73e5e9bb324fa9d04a95..bd6a6f64d5eb141da9b372429c0ed453c045294b 100644
--- a/test/unit/rtree.cc
+++ b/test/unit/rtree.cc
@@ -36,7 +36,8 @@ simple_check()
 	header();
 
 	struct rtree tree;
-	rtree_init(&tree, 2, extent_size, extent_alloc, extent_free);
+	rtree_init(&tree, 2, extent_size, extent_alloc, extent_free,
+		   RTREE_EUCLID);
 
 	printf("Insert 1..X, remove 1..X\n");
 	for (size_t i = 1; i <= rounds; i++) {
@@ -228,7 +229,8 @@ neighbor_test()
 
 	for (size_t i = 0; i <= test_count; i++) {
 		struct rtree tree;
-		rtree_init(&tree, 2, extent_size, extent_alloc, extent_free);
+		rtree_init(&tree, 2, extent_size, extent_alloc, extent_free,
+			   RTREE_EUCLID);
 
 		rtree_test_build(&tree, arr, i);
 
diff --git a/test/unit/rtree_itr.cc b/test/unit/rtree_itr.cc
index 088edd657e1ca3e929247251656a931037ce263d..3c6c3c3fdc1e7d46dc59a759da1a26b3fe9180c6 100644
--- a/test/unit/rtree_itr.cc
+++ b/test/unit/rtree_itr.cc
@@ -30,7 +30,8 @@ itr_check()
 	header();
 
 	struct rtree tree;
-	rtree_init(&tree, 2, extent_size, extent_alloc, extent_free);
+	rtree_init(&tree, 2, extent_size, extent_alloc, extent_free,
+		   RTREE_EUCLID);
 
 	/* Filling tree */
 	const size_t count1 = 10000;
@@ -211,7 +212,8 @@ itr_invalidate_check()
 			del_cnt = test_size - del_pos;
 		}
 		struct rtree tree;
-		rtree_init(&tree, 2, extent_size, extent_alloc, extent_free);
+		rtree_init(&tree, 2, extent_size, extent_alloc, extent_free,
+			   RTREE_EUCLID);
 		struct rtree_iterator iterators[test_size];
 		for (size_t i = 0; i < test_size; i++)
 			rtree_iterator_init(iterators + i);
@@ -255,7 +257,8 @@ itr_invalidate_check()
 		size_t ins_cnt = rand() % max_insert_count + 1;
 
 		struct rtree tree;
-		rtree_init(&tree, 2, extent_size, extent_alloc, extent_free);
+		rtree_init(&tree, 2, extent_size, extent_alloc, extent_free,
+			   RTREE_EUCLID);
 		struct rtree_iterator iterators[test_size];
 		for (size_t i = 0; i < test_size; i++)
 			rtree_iterator_init(iterators + i);
diff --git a/test/unit/rtree_multidim.cc b/test/unit/rtree_multidim.cc
index 4d67179e47d47437180bf4fb6e1770bf27ffaaa2..70c11b2b9f3b35f31f608a8f4d169276bec73a93 100644
--- a/test/unit/rtree_multidim.cc
+++ b/test/unit/rtree_multidim.cc
@@ -139,6 +139,20 @@ struct CBox {
 		}
 		return res;
 	}
+	coord_t DistanceMan(const CBox<DIMENSION> &point) const
+	{
+		coord_t res = 0;
+		for (unsigned i = 0; i < DIMENSION; i++) {
+			if (point.pairs[i].a < pairs[i].a) {
+				coord_t d = pairs[i].a - point.pairs[i].a;
+				res += d;
+			} else if (point.pairs[i].a > pairs[i].b) {
+				coord_t d = point.pairs[i].a - pairs[i].b;
+				res += d;
+			}
+		}
+		return res;
+	}
 };
 
 template<unsigned DIMENSION>
@@ -215,6 +229,8 @@ struct CBoxSet {
 	}
 	void SelectNeigh(const CBox<DIMENSION> &point,
 			 vector<CBoxSetEntry<DIMENSION> > &result) const;
+	void SelectNeighMan(const CBox<DIMENSION> &point,
+			    vector<CBoxSetEntry<DIMENSION> > &result) const;
 };
 
 template<unsigned DIMENSION>
@@ -230,9 +246,22 @@ struct CEntryByDistance {
 	}
 };
 
+template<unsigned DIMENSION>
+struct CEntryByDistanceMan {
+	const CBox<DIMENSION> &point;
+	CEntryByDistanceMan(const CBox<DIMENSION> &point_) : point(point_) {}
+	bool operator()(const CBoxSetEntry<DIMENSION> &a,
+			const CBoxSetEntry<DIMENSION> &b) const
+	{
+		coord_t da = a.box.DistanceMan(point);
+		coord_t db = b.box.DistanceMan(point);
+		return da < db ? true : da > db ? false : a.id < b.id;
+	}
+};
+
 template<unsigned DIMENSION>
 void CBoxSet<DIMENSION>::SelectNeigh(const CBox<DIMENSION> &point,
-	vector<CBoxSetEntry<DIMENSION> > &result) const
+				     vector<CBoxSetEntry<DIMENSION> > &result) const
 {
 	result.clear();
 	CEntryByDistance<DIMENSION> comp(point);
@@ -243,6 +272,8 @@ void CBoxSet<DIMENSION>::SelectNeigh(const CBox<DIMENSION> &point,
 			continue;
 		set.insert(entries[i]);
 	}
+	if (set.empty())
+		return;
 	coord_t max_d = set.rbegin()->box.Distance2(point);
 	for (; i < entries.size(); i++) {
 		if (!entries[i].used)
@@ -260,6 +291,38 @@ void CBoxSet<DIMENSION>::SelectNeigh(const CBox<DIMENSION> &point,
 		result.push_back(itr);
 }
 
+template<unsigned DIMENSION>
+void CBoxSet<DIMENSION>::SelectNeighMan(const CBox<DIMENSION> &point,
+	vector<CBoxSetEntry<DIMENSION> > &result) const
+{
+	result.clear();
+	CEntryByDistanceMan<DIMENSION> comp(point);
+	set<CBoxSetEntry<DIMENSION>, CEntryByDistanceMan<DIMENSION> > set(comp);
+	size_t i = 0;
+	for (; i < entries.size() && set.size() < NEIGH_COUNT; i++) {
+		if (!entries[i].used)
+			continue;
+		set.insert(entries[i]);
+	}
+	if (set.empty())
+		return;
+	coord_t max_d = set.rbegin()->box.DistanceMan(point);
+	for (; i < entries.size(); i++) {
+		if (!entries[i].used)
+			continue;
+		coord_t d = entries[i].box.DistanceMan(point);
+		if (d < max_d) {
+			auto itr = set.end();
+			--itr;
+			set.erase(itr);
+			set.insert(entries[i]);
+			max_d = set.rbegin()->box.DistanceMan(point);
+		}
+	}
+	for (auto itr : set)
+		result.push_back(itr);
+}
+
 template<unsigned DIMENSION>
 static void
 test_select_neigh(const CBoxSet<DIMENSION> &set, const struct rtree *tree)
@@ -291,10 +354,51 @@ test_select_neigh(const CBoxSet<DIMENSION> &set, const struct rtree *tree)
 	} else {
 		for (size_t i = 0; i < res1.size(); i++)
 			if (res1[i].id != res2[i].id &&
-				res1[i].box.Distance2(box) !=
-				res2[i].box.Distance2(box))
+			    res1[i].box.Distance2(box) !=
+			    res2[i].box.Distance2(box))
+				printf("%s result differ!\n", __func__);
+	}
+	rtree_iterator_destroy(&iterator);
+
+}
+
+template<unsigned DIMENSION>
+static void
+test_select_neigh_man(const CBoxSet<DIMENSION> &set, struct rtree *tree)
+{
+	CBox<DIMENSION> box;
+	box.RandomizeBig();
+	vector<CBoxSetEntry<DIMENSION> > res1;
+	set.SelectNeighMan(box, res1);
+
+	struct rtree_rect rt;
+	box.FillRTreeRect(&rt);
+	struct rtree_iterator iterator;
+	rtree_iterator_init(&iterator);
+	vector<CBoxSetEntry<DIMENSION> > res2;
+	tree->distance_type = RTREE_MANHATTAN; /* dirty hack */
+	if (rtree_search(tree, &rt, SOP_NEIGHBOR, &iterator)) {
+		void *record;
+		while((record = rtree_iterator_next(&iterator))) {
+			CBoxSetEntry<DIMENSION> entry;
+			entry.id = ((unsigned)(uintptr_t)record) - 1;
+			entry.box = set.entries[entry.id].box;
+			res2.push_back(entry);
+			if (res2.size() == NEIGH_COUNT)
+				break;
+		}
+	}
+	if (res1.size() != res2.size()) {
+		printf("%s result size differ %d %d\n", __func__,
+		       (int)res1.size(), (int)res2.size());
+	} else {
+		for (size_t i = 0; i < res1.size(); i++)
+			if (res1[i].id != res2[i].id &&
+			    res1[i].box.DistanceMan(box) !=
+			    res2[i].box.DistanceMan(box))
 				printf("%s result differ!\n", __func__);
 	}
+	tree->distance_type = RTREE_EUCLID; /* dirty hack */
 	rtree_iterator_destroy(&iterator);
 
 }
@@ -382,7 +486,8 @@ rand_test()
 	CBoxSet<DIMENSION> set;
 
 	struct rtree tree;
-	rtree_init(&tree, DIMENSION, extent_size, extent_alloc, extent_free);
+	rtree_init(&tree, DIMENSION, extent_size, extent_alloc, extent_free,
+		   RTREE_EUCLID);
 
 	printf("\tDIMENSION: %u, page size: %u, max fill: %u\n",
 	       DIMENSION, tree.page_size, tree.page_max_fill);
@@ -412,6 +517,7 @@ rand_test()
 		}
 		assert(set.boxCount == tree.n_records);
 		test_select_neigh<DIMENSION>(set, &tree);
+		test_select_neigh_man<DIMENSION>(set, &tree);
 		test_select_in<DIMENSION>(set, &tree);
 		test_select_strict_in<DIMENSION>(set, &tree);
 	}
diff --git a/test/wal_off/oom.result b/test/wal_off/oom.result
index 2098f2725791a8c293fbd0b9cd55f7db6ab17d32..c629d4662349dc51444cbac6de7bf947fcb6f021 100644
--- a/test/wal_off/oom.result
+++ b/test/wal_off/oom.result
@@ -203,3 +203,63 @@ space:drop()
 t = nil
 ---
 ...
+-- https://github.com/tarantool/tarantool/issues/962 index:delete() failed
+--# stop server default
+--# start server default
+arena_bytes = box.cfg.slab_alloc_arena * 1024 * 1024 * 1024
+---
+...
+str = string.rep('a', 15000) -- about size of index memory block
+---
+...
+space = box.schema.space.create('tweedledum')
+---
+...
+index = space:create_index('primary', { type = 'hash' })
+---
+...
+for i=1,10000 do space:insert{i, str} end
+---
+- error: Failed to allocate 15019 bytes in slab allocator for tuple
+...
+definatelly_used = index:count() * 16 * 1024
+---
+...
+2 * definatelly_used > arena_bytes -- at least half memory used
+---
+- true
+...
+to_del = index:count()
+---
+...
+for i=1,to_del do space:delete{i} end
+---
+...
+index:count()
+---
+- 0
+...
+for i=1,10000 do space:insert{i, str} end
+---
+- error: Failed to allocate 15019 bytes in slab allocator for tuple
+...
+definatelly_used = index:count() * 16 * 1024
+---
+...
+2 * definatelly_used > arena_bytes -- at least half memory used
+---
+- true
+...
+space:truncate()
+---
+...
+index:count()
+---
+- 0
+...
+space:drop()
+---
+...
+str = nil
+---
+...
diff --git a/test/wal_off/oom.test.lua b/test/wal_off/oom.test.lua
index f269c68a689141202c4b3b04f8a2abc1757177b2..2ebdf96f816cec4bb81136a522dbeaad4901ee6e 100644
--- a/test/wal_off/oom.test.lua
+++ b/test/wal_off/oom.test.lua
@@ -75,3 +75,29 @@ space:select{}
 --
 space:drop()
 t = nil
+
+-- https://github.com/tarantool/tarantool/issues/962 index:delete() failed
+--# stop server default
+--# start server default
+arena_bytes = box.cfg.slab_alloc_arena * 1024 * 1024 * 1024
+str = string.rep('a', 15000) -- about size of index memory block
+
+space = box.schema.space.create('tweedledum')
+index = space:create_index('primary', { type = 'hash' })
+
+for i=1,10000 do space:insert{i, str} end
+definatelly_used = index:count() * 16 * 1024
+2 * definatelly_used > arena_bytes -- at least half memory used
+to_del = index:count()
+for i=1,to_del do space:delete{i} end
+index:count()
+
+for i=1,10000 do space:insert{i, str} end
+definatelly_used = index:count() * 16 * 1024
+2 * definatelly_used > arena_bytes -- at least half memory used
+space:truncate()
+index:count()
+
+space:drop()
+str = nil
+
diff --git a/third_party/pmatomic.h b/third_party/pmatomic.h
new file mode 100644
index 0000000000000000000000000000000000000000..0b63e2e0c708ed211fa64e25edf9c0fecd332d23
--- /dev/null
+++ b/third_party/pmatomic.h
@@ -0,0 +1,494 @@
+/*-
+ * pmatomic.h - Poor Man's atomics
+ *
+ * Borrowed from FreeBSD (original copyright follows).
+ *
+ * Standard atomic facilities in stdatomic.h are great, unless you are
+ * stuck with an old compiler, or you attempt to compile code using
+ * stdatomic.h in C++ mode [gcc 4.9], or if you were desperate enough to
+ * enable OpenMP in C mode [gcc 4.9].
+ *
+ * There are several discrepancies between gcc and clang, namely clang
+ * refuses to apply atomic operations to non-atomic types while gcc is
+ * more tolerant.
+ *
+ * For these reasons we provide a custom implementation of operations on
+ * atomic types:
+ *
+ *   A. same names/semantics as in stdatomic.h;
+ *   B. all names prefixed with 'pm_' to avoid name collisions;
+ *   C. applicable to non-atomic types.
+ *
+ * Ex:
+ *     int i;
+ *     pm_atomic_fetch_add_explicit(&i, 1, pm_memory_order_relaxed);
+ *
+ * Note: do NOT use _Atomic keyword (see gcc issues above).
+ */
+
+/*-
+ * Migration strategy
+ *
+ * Switching to <stdatomic.h> will be relatively easy. A
+ * straightforward text replace on the codebase removes 'pm_' prefix
+ * in names. Compiling with clang reveals missing _Atomic qualifiers.
+ */
+
+/*-
+ * Logistics
+ *
+ * In order to make it possible to merge with the updated upstream we
+ * restrict modifications in this file to the bare minimum. For this
+ * reason we comment unused code regions with #if 0 instead of removing
+ * them.
+ *
+ * Renames are carried out by a script generating the final header.
+ */
+
+/*-
+ * Copyright (c) 2011 Ed Schouten <ed@FreeBSD.org>
+ *                    David Chisnall <theraven@FreeBSD.org>
+ * All rights reserved.
+ *
+ * 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 THE AUTHOR AND CONTRIBUTORS ``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 THE AUTHOR 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.
+ *
+ * $FreeBSD: releng/10.1/sys/sys/stdatomic.h 264496 2014-04-15 09:41:52Z tijl $
+ */
+
+#ifndef PMATOMIC_H__
+#define	PMATOMIC_H__
+
+/* Compiler-fu */
+#if !defined(__has_feature)
+#define __has_feature(x) 0
+#endif
+#if !defined(__has_builtin)
+#define __has_builtin(x) __has_feature(x)
+#endif
+#if !defined(__GNUC_PREREQ__)
+#if defined(__GNUC__) && defined(__GNUC_MINOR__)
+#define __GNUC_PREREQ__(maj, min)					\
+	((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
+#else
+#define __GNUC_PREREQ__(maj, min) 0
+#endif
+#endif
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+/*
+ * Removed __PM_CLANG_ATOMICS clause, this is because
+ * 1) clang understands gcc intrinsics as well;
+ * 2) clang intrinsics require _Atomic quialified types while gcc ones
+ *    don't.
+ */
+#if __GNUC_PREREQ__(4, 7)
+#define	__PM_GNUC_ATOMICS
+#elif defined(__GNUC__)
+#define	__PM_SYNC_ATOMICS
+#else
+#error "pmatomic.h does not support your compiler"
+#endif
+
+/*
+ * 7.17.1 Atomic lock-free macros.
+ */
+#if 0
+
+#ifdef __GCC_ATOMIC_BOOL_LOCK_FREE
+#define	ATOMIC_BOOL_LOCK_FREE		__GCC_ATOMIC_BOOL_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_CHAR_LOCK_FREE
+#define	ATOMIC_CHAR_LOCK_FREE		__GCC_ATOMIC_CHAR_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_CHAR16_T_LOCK_FREE
+#define	ATOMIC_CHAR16_T_LOCK_FREE	__GCC_ATOMIC_CHAR16_T_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_CHAR32_T_LOCK_FREE
+#define	ATOMIC_CHAR32_T_LOCK_FREE	__GCC_ATOMIC_CHAR32_T_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_WCHAR_T_LOCK_FREE
+#define	ATOMIC_WCHAR_T_LOCK_FREE	__GCC_ATOMIC_WCHAR_T_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_SHORT_LOCK_FREE
+#define	ATOMIC_SHORT_LOCK_FREE		__GCC_ATOMIC_SHORT_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_INT_LOCK_FREE
+#define	ATOMIC_INT_LOCK_FREE		__GCC_ATOMIC_INT_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_LONG_LOCK_FREE
+#define	ATOMIC_LONG_LOCK_FREE		__GCC_ATOMIC_LONG_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_LLONG_LOCK_FREE
+#define	ATOMIC_LLONG_LOCK_FREE		__GCC_ATOMIC_LLONG_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_POINTER_LOCK_FREE
+#define	ATOMIC_POINTER_LOCK_FREE	__GCC_ATOMIC_POINTER_LOCK_FREE
+#endif
+
+#endif
+
+/*
+ * 7.17.2 Initialization.
+ */
+#if 0
+
+#if defined(__PM_CLANG_ATOMICS)
+#define	ATOMIC_VAR_INIT(value)		(value)
+#define	atomic_init(obj, value)		__c11_atomic_init(obj, value)
+#else
+#define	ATOMIC_VAR_INIT(value)		{ .__val = (value) }
+#define	atomic_init(obj, value)		((void)((obj)->__val = (value)))
+#endif
+
+#endif
+
+/*
+ * Clang and recent GCC both provide predefined macros for the memory
+ * orderings.  If we are using a compiler that doesn't define them, use the
+ * clang values - these will be ignored in the fallback path.
+ */
+
+#ifndef __ATOMIC_RELAXED
+#define __ATOMIC_RELAXED		0
+#endif
+#ifndef __ATOMIC_CONSUME
+#define __ATOMIC_CONSUME		1
+#endif
+#ifndef __ATOMIC_ACQUIRE
+#define __ATOMIC_ACQUIRE		2
+#endif
+#ifndef __ATOMIC_RELEASE
+#define __ATOMIC_RELEASE		3
+#endif
+#ifndef __ATOMIC_ACQ_REL
+#define __ATOMIC_ACQ_REL		4
+#endif
+#ifndef __ATOMIC_SEQ_CST
+#define __ATOMIC_SEQ_CST		5
+#endif
+
+/*
+ * 7.17.3 Order and consistency.
+ *
+ * The pm_memory_order_* constants that denote the barrier behaviour of the
+ * atomic operations.
+ */
+
+typedef enum {
+	pm_memory_order_relaxed = __ATOMIC_RELAXED,
+	pm_memory_order_consume = __ATOMIC_CONSUME,
+	pm_memory_order_acquire = __ATOMIC_ACQUIRE,
+	pm_memory_order_release = __ATOMIC_RELEASE,
+	pm_memory_order_acq_rel = __ATOMIC_ACQ_REL,
+	pm_memory_order_seq_cst = __ATOMIC_SEQ_CST
+} pm_memory_order;
+
+/*
+ * 7.17.4 Fences.
+ */
+
+static __inline void
+pm_atomic_thread_fence(pm_memory_order __order __attribute__((__unused__)))
+{
+
+#ifdef __PM_CLANG_ATOMICS
+	__c11_atomic_thread_fence(__order);
+#elif defined(__PM_GNUC_ATOMICS)
+	__atomic_thread_fence(__order);
+#else
+	__sync_synchronize();
+#endif
+}
+
+static __inline void
+pm_atomic_signal_fence(pm_memory_order __order __attribute__((__unused__)))
+{
+
+#ifdef __PM_CLANG_ATOMICS
+	__c11_atomic_signal_fence(__order);
+#elif defined(__PM_GNUC_ATOMICS)
+	__atomic_signal_fence(__order);
+#else
+	__asm volatile ("" ::: "memory");
+#endif
+}
+
+/*
+ * 7.17.5 Lock-free property.
+ */
+#if 0
+
+#if defined(_KERNEL)
+/* Atomics in kernelspace are always lock-free. */
+#define	atomic_is_lock_free(obj) \
+	((void)(obj), (bool)1)
+#elif defined(__PM_CLANG_ATOMICS)
+#define	atomic_is_lock_free(obj) \
+	__atomic_is_lock_free(sizeof(*(obj)), obj)
+#elif defined(__PM_GNUC_ATOMICS)
+#define	atomic_is_lock_free(obj) \
+	__atomic_is_lock_free(sizeof((obj)->__val), &(obj)->__val)
+#else
+#define	atomic_is_lock_free(obj) \
+	((void)(obj), sizeof((obj)->__val) <= sizeof(void *))
+#endif
+
+#endif
+
+/*
+ * 7.17.6 Atomic integer types.
+ */
+#if 0
+
+typedef _Atomic(bool)			atomic_bool;
+typedef _Atomic(char)			atomic_char;
+typedef _Atomic(signed char)		atomic_schar;
+typedef _Atomic(unsigned char)		atomic_uchar;
+typedef _Atomic(short)			atomic_short;
+typedef _Atomic(unsigned short)		atomic_ushort;
+typedef _Atomic(int)			atomic_int;
+typedef _Atomic(unsigned int)		atomic_uint;
+typedef _Atomic(long)			atomic_long;
+typedef _Atomic(unsigned long)		atomic_ulong;
+typedef _Atomic(long long)		atomic_llong;
+typedef _Atomic(unsigned long long)	atomic_ullong;
+typedef _Atomic(__char16_t)		atomic_char16_t;
+typedef _Atomic(__char32_t)		atomic_char32_t;
+typedef _Atomic(___wchar_t)		atomic_wchar_t;
+typedef _Atomic(__int_least8_t)		atomic_int_least8_t;
+typedef _Atomic(__uint_least8_t)	atomic_uint_least8_t;
+typedef _Atomic(__int_least16_t)	atomic_int_least16_t;
+typedef _Atomic(__uint_least16_t)	atomic_uint_least16_t;
+typedef _Atomic(__int_least32_t)	atomic_int_least32_t;
+typedef _Atomic(__uint_least32_t)	atomic_uint_least32_t;
+typedef _Atomic(__int_least64_t)	atomic_int_least64_t;
+typedef _Atomic(__uint_least64_t)	atomic_uint_least64_t;
+typedef _Atomic(__int_fast8_t)		atomic_int_fast8_t;
+typedef _Atomic(__uint_fast8_t)		atomic_uint_fast8_t;
+typedef _Atomic(__int_fast16_t)		atomic_int_fast16_t;
+typedef _Atomic(__uint_fast16_t)	atomic_uint_fast16_t;
+typedef _Atomic(__int_fast32_t)		atomic_int_fast32_t;
+typedef _Atomic(__uint_fast32_t)	atomic_uint_fast32_t;
+typedef _Atomic(__int_fast64_t)		atomic_int_fast64_t;
+typedef _Atomic(__uint_fast64_t)	atomic_uint_fast64_t;
+typedef _Atomic(__intptr_t)		atomic_intptr_t;
+typedef _Atomic(__uintptr_t)		atomic_uintptr_t;
+typedef _Atomic(__size_t)		atomic_size_t;
+typedef _Atomic(__ptrdiff_t)		atomic_ptrdiff_t;
+typedef _Atomic(__intmax_t)		atomic_intmax_t;
+typedef _Atomic(__uintmax_t)		atomic_uintmax_t;
+
+#endif
+
+/*
+ * 7.17.7 Operations on atomic types.
+ */
+
+/*
+ * Compiler-specific operations.
+ */
+
+#if defined(__PM_CLANG_ATOMICS)
+#define	pm_atomic_compare_exchange_strong_explicit(object, expected,	\
+    desired, success, failure)						\
+	__c11_atomic_compare_exchange_strong(object, expected, desired,	\
+	    success, failure)
+#define	pm_atomic_compare_exchange_weak_explicit(object, expected,		\
+    desired, success, failure)						\
+	__c11_atomic_compare_exchange_weak(object, expected, desired,	\
+	    success, failure)
+#define	pm_atomic_exchange_explicit(object, desired, order)		\
+	__c11_atomic_exchange(object, desired, order)
+#define	pm_atomic_fetch_add_explicit(object, operand, order)		\
+	__c11_atomic_fetch_add(object, operand, order)
+#define	pm_atomic_fetch_and_explicit(object, operand, order)		\
+	__c11_atomic_fetch_and(object, operand, order)
+#define	pm_atomic_fetch_or_explicit(object, operand, order)		\
+	__c11_atomic_fetch_or(object, operand, order)
+#define	pm_atomic_fetch_sub_explicit(object, operand, order)		\
+	__c11_atomic_fetch_sub(object, operand, order)
+#define	pm_atomic_fetch_xor_explicit(object, operand, order)		\
+	__c11_atomic_fetch_xor(object, operand, order)
+#define	pm_atomic_load_explicit(object, order)				\
+	__c11_atomic_load(object, order)
+#define	pm_atomic_store_explicit(object, desired, order)			\
+	__c11_atomic_store(object, desired, order)
+#elif defined(__PM_GNUC_ATOMICS)
+#define	pm_atomic_compare_exchange_strong_explicit(object, expected,	\
+    desired, success, failure)						\
+	__atomic_compare_exchange_n(object, expected,		\
+	    desired, 0, success, failure)
+#define	pm_atomic_compare_exchange_weak_explicit(object, expected,		\
+    desired, success, failure)						\
+	__atomic_compare_exchange_n(object, expected,		\
+	    desired, 1, success, failure)
+#define	pm_atomic_exchange_explicit(object, desired, order)		\
+	__atomic_exchange_n(object, desired, order)
+#define	pm_atomic_fetch_add_explicit(object, operand, order)		\
+	__atomic_fetch_add(object, operand, order)
+#define	pm_atomic_fetch_and_explicit(object, operand, order)		\
+	__atomic_fetch_and(object, operand, order)
+#define	pm_atomic_fetch_or_explicit(object, operand, order)		\
+	__atomic_fetch_or(object, operand, order)
+#define	pm_atomic_fetch_sub_explicit(object, operand, order)		\
+	__atomic_fetch_sub(object, operand, order)
+#define	pm_atomic_fetch_xor_explicit(object, operand, order)		\
+	__atomic_fetch_xor(object, operand, order)
+#define	pm_atomic_load_explicit(object, order)				\
+	__atomic_load_n(object, order)
+#define	pm_atomic_store_explicit(object, desired, order)			\
+	__atomic_store_n(object, desired, order)
+#else
+#define	__pm_atomic_apply_stride(object, operand) \
+	(((__typeof__(*(object)))0) + (operand))
+#define	pm_atomic_compare_exchange_strong_explicit(object, expected,	\
+    desired, success, failure)	__extension__ ({			\
+	__typeof__(expected) __ep = (expected);				\
+	__typeof__(*__ep) __e = *__ep;					\
+	(void)(success); (void)(failure);				\
+	(bool)((*__ep = __sync_val_compare_and_swap(object,	\
+	    __e, desired)) == __e);					\
+})
+#define	pm_atomic_compare_exchange_weak_explicit(object, expected,		\
+    desired, success, failure)						\
+	pm_atomic_compare_exchange_strong_explicit(object, expected,	\
+		desired, success, failure)
+#if __has_builtin(__sync_swap)
+/* Clang provides a full-barrier atomic exchange - use it if available. */
+#define	pm_atomic_exchange_explicit(object, desired, order)		\
+	((void)(order), __sync_swap(object, desired))
+#else
+/*
+ * __sync_lock_test_and_set() is only an acquire barrier in theory (although in
+ * practice it is usually a full barrier) so we need an explicit barrier before
+ * it.
+ */
+#define	pm_atomic_exchange_explicit(object, desired, order)		\
+__extension__ ({							\
+	__typeof__(object) __o = (object);				\
+	__typeof__(desired) __d = (desired);				\
+	(void)(order);							\
+	__sync_synchronize();						\
+	__sync_lock_test_and_set(__o, __d);			\
+})
+#endif
+#define	pm_atomic_fetch_add_explicit(object, operand, order)		\
+	((void)(order), __sync_fetch_and_add(object,		\
+	    __pm_atomic_apply_stride(object, operand)))
+#define	pm_atomic_fetch_and_explicit(object, operand, order)		\
+	((void)(order), __sync_fetch_and_and(object, operand))
+#define	pm_atomic_fetch_or_explicit(object, operand, order)		\
+	((void)(order), __sync_fetch_and_or(object, operand))
+#define	pm_atomic_fetch_sub_explicit(object, operand, order)		\
+	((void)(order), __sync_fetch_and_sub(object,		\
+	    __pm_atomic_apply_stride(object, operand)))
+#define	pm_atomic_fetch_xor_explicit(object, operand, order)		\
+	((void)(order), __sync_fetch_and_xor(object, operand))
+#define	pm_atomic_load_explicit(object, order)				\
+	((void)(order), __sync_fetch_and_add(object, 0))
+#define	pm_atomic_store_explicit(object, desired, order)			\
+	((void)pm_atomic_exchange_explicit(object, desired, order))
+#endif
+
+/*
+ * Convenience functions.
+ *
+ * Don't provide these in kernel space. In kernel space, we should be
+ * disciplined enough to always provide explicit barriers.
+ */
+
+#ifndef _KERNEL
+#define	pm_atomic_compare_exchange_strong(object, expected, desired)	\
+	pm_atomic_compare_exchange_strong_explicit(object, expected,	\
+	    desired, pm_memory_order_seq_cst, pm_memory_order_seq_cst)
+#define	pm_atomic_compare_exchange_weak(object, expected, desired)		\
+	pm_atomic_compare_exchange_weak_explicit(object, expected,		\
+	    desired, pm_memory_order_seq_cst, pm_memory_order_seq_cst)
+#define	pm_atomic_exchange(object, desired)				\
+	pm_atomic_exchange_explicit(object, desired, pm_memory_order_seq_cst)
+#define	pm_atomic_fetch_add(object, operand)				\
+	pm_atomic_fetch_add_explicit(object, operand, pm_memory_order_seq_cst)
+#define	pm_atomic_fetch_and(object, operand)				\
+	pm_atomic_fetch_and_explicit(object, operand, pm_memory_order_seq_cst)
+#define	pm_atomic_fetch_or(object, operand)				\
+	pm_atomic_fetch_or_explicit(object, operand, pm_memory_order_seq_cst)
+#define	pm_atomic_fetch_sub(object, operand)				\
+	pm_atomic_fetch_sub_explicit(object, operand, pm_memory_order_seq_cst)
+#define	pm_atomic_fetch_xor(object, operand)				\
+	pm_atomic_fetch_xor_explicit(object, operand, pm_memory_order_seq_cst)
+#define	pm_atomic_load(object)						\
+	pm_atomic_load_explicit(object, pm_memory_order_seq_cst)
+#define	pm_atomic_store(object, desired)					\
+	pm_atomic_store_explicit(object, desired, pm_memory_order_seq_cst)
+#endif /* !_KERNEL */
+
+/*
+ * 7.17.8 Atomic flag type and operations.
+ *
+ * XXX: Assume atomic_bool can be used as an atomic_flag. Is there some
+ * kind of compiler built-in type we could use?
+ */
+#if 0
+
+typedef struct {
+	atomic_bool	__flag;
+} atomic_flag;
+
+#define	ATOMIC_FLAG_INIT		{ ATOMIC_VAR_INIT(0) }
+
+static __inline bool
+atomic_flag_test_and_set_explicit(volatile atomic_flag *__object,
+    pm_memory_order __order)
+{
+	return (pm_atomic_exchange_explicit(&__object->__flag, 1, __order));
+}
+
+static __inline void
+atomic_flag_clear_explicit(volatile atomic_flag *__object, pm_memory_order __order)
+{
+
+	pm_atomic_store_explicit(&__object->__flag, 0, __order);
+}
+
+#ifndef _KERNEL
+static __inline bool
+atomic_flag_test_and_set(volatile atomic_flag *__object)
+{
+
+	return (atomic_flag_test_and_set_explicit(__object,
+	    pm_memory_order_seq_cst));
+}
+
+static __inline void
+atomic_flag_clear(volatile atomic_flag *__object)
+{
+
+	atomic_flag_clear_explicit(__object, pm_memory_order_seq_cst);
+}
+#endif /* !_KERNEL */
+
+#endif
+
+#endif /* !_STDATOMIC_H_ */
diff --git a/third_party/pmatomic/add-pm-prefix-filt.py b/third_party/pmatomic/add-pm-prefix-filt.py
new file mode 100644
index 0000000000000000000000000000000000000000..ee81a93dec52bc5aa643198bfd0d0e9ceebb3f12
--- /dev/null
+++ b/third_party/pmatomic/add-pm-prefix-filt.py
@@ -0,0 +1,49 @@
+#! /usr/bin/env python
+# Append 'pm_' prefix (used to generate pmatomic.h from pmatomic.h.proto)
+
+subst=r"""
+_Bool bool
+&\(object\)->__val object
+\(object\)->__val *(object)
+&\(__o\)->__val __o
+__CLANG_ATOMICS __PM_CLANG_ATOMICS
+__GNUC_ATOMICS __PM_GNUC_ATOMICS
+__SYNC_ATOMICS __PM_SYNC_ATOMICS
+__atomic_apply_stride __pm_atomic_apply_stride
+atomic_compare_exchange_strong pm_atomic_compare_exchange_strong
+atomic_compare_exchange_strong_explicit pm_atomic_compare_exchange_strong_explicit
+atomic_compare_exchange_weak pm_atomic_compare_exchange_weak
+atomic_compare_exchange_weak_explicit pm_atomic_compare_exchange_weak_explicit
+atomic_exchange pm_atomic_exchange
+atomic_exchange_explicit pm_atomic_exchange_explicit
+atomic_fetch_add pm_atomic_fetch_add
+atomic_fetch_add_explicit pm_atomic_fetch_add_explicit
+atomic_fetch_and pm_atomic_fetch_and
+atomic_fetch_and_explicit pm_atomic_fetch_and_explicit
+atomic_fetch_or pm_atomic_fetch_or
+atomic_fetch_or_explicit pm_atomic_fetch_or_explicit
+atomic_fetch_sub pm_atomic_fetch_sub
+atomic_fetch_sub_explicit pm_atomic_fetch_sub_explicit
+atomic_fetch_xor pm_atomic_fetch_xor
+atomic_fetch_xor_explicit pm_atomic_fetch_xor_explicit
+atomic_load pm_atomic_load
+atomic_load_explicit pm_atomic_load_explicit
+atomic_signal_fence pm_atomic_signal_fence
+atomic_store pm_atomic_store
+atomic_store_explicit pm_atomic_store_explicit
+atomic_thread_fence pm_atomic_thread_fence
+memory_order pm_memory_order
+memory_order_acq_rel pm_memory_order_acq_rel
+memory_order_acquire pm_memory_order_acquire
+memory_order_consume pm_memory_order_consume
+memory_order_relaxed pm_memory_order_relaxed
+memory_order_release pm_memory_order_release
+memory_order_seq_cst pm_memory_order_seq_cst"""
+
+import sys
+import re
+data = sys.stdin.read()
+for pattern, repl in (ln.split(' ',2) for ln in subst.splitlines() if ln):
+    data = re.sub(r'(?<=\W)'+pattern, repl, data)
+sys.stdout.write(data)
+
diff --git a/third_party/pmatomic/pmatomic.h.proto b/third_party/pmatomic/pmatomic.h.proto
new file mode 100644
index 0000000000000000000000000000000000000000..ec18eaafca7962b73c6f45a732149ac1a6c8f04c
--- /dev/null
+++ b/third_party/pmatomic/pmatomic.h.proto
@@ -0,0 +1,493 @@
+/*-
+ * pmatomic.h - Poor Man's atomics
+ *
+ * Borrowed from FreeBSD (original copyright follows).
+ *
+ * Standard atomic facilities in stdatomic.h are great, unless you are
+ * stuck with an old compiler, or you attemt to compile a code using
+ * stdatomic.h in C++ mode [gcc 4.9], or if you were desperate enough to
+ * enable OpenMP in C mode [gcc 4.9].
+ *
+ * There are several discrepancies between gcc and clang, namely clang
+ * refuses to apply atomic operations to non-atomic types while gcc is
+ * more tolerant.
+ *
+ * For these reasons we provide a custom implementation of operations on
+ * atomic types:
+ *
+ *   A. same names/semantics as in stdatomic.h;
+ *   B. all names prefixed with 'pm_' to avoid name collisions;
+ *   C. applicable to non-atomic types.
+ *
+ * Ex:
+ *     int i;
+ *     pm_atomic_fetch_add_explicit(&i, 1, pm_memory_order_relaxed);
+ *
+ * Note: do NOT use _Atomic keyword (see gcc issues above).
+ */
+
+/*-
+ * Migration strategy
+ *
+ * Switching to <stdatomic.h> will be relatively easy. A
+ * straightforward text replace on the codebase removes 'pm_' prefix
+ * in names. Compiling with clang reveals missing _Atomic qualifiers.
+ */
+
+/*-
+ * Logistics
+ *
+ * In order to make it possible to merge with the updated upstream we
+ * restrict modifications in this file to the bare minimum. For this
+ * reason we comment unused code regions with #if 0 instead of removing
+ * them.
+ *
+ * Renames are carried out by a scipt generating the final header.
+ */
+
+/*-
+ * Copyright (c) 2011 Ed Schouten <ed@FreeBSD.org>
+ *                    David Chisnall <theraven@FreeBSD.org>
+ * All rights reserved.
+ *
+ * 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 THE AUTHOR AND CONTRIBUTORS ``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 THE AUTHOR 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.
+ *
+ * $FreeBSD: releng/10.1/sys/sys/stdatomic.h 264496 2014-04-15 09:41:52Z tijl $
+ */
+
+#ifndef PMATOMIC_H__
+#define	PMATOMIC_H__
+
+/* Compiler-fu */
+#if !defined(__has_feature)
+#define __has_feature(x) 0
+#endif
+#if !defined(__has_builtin)
+#define __has_builtin(x) __has_feature(x)
+#endif
+#if !defined(__GNUC_PREREQ__)
+#if defined(__GNUC__) && defined(__GNUC_MINOR__)
+#define __GNUC_PREREQ__(maj, min)					\
+	((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
+#else
+#define __GNUC_PREREQ__(maj, min) 0
+#endif
+#endif
+
+#include <stddef.h>
+#include <stdint.h>
+
+/*
+ * Removed __CLANG_ATOMICS clause, this is because
+ * 1) clang understands gcc intrinsics as well;
+ * 2) clang intrinsics require _Atomic quialified types while gcc ones
+ *    don't.
+ */
+#if __GNUC_PREREQ__(4, 7)
+#define	__GNUC_ATOMICS
+#elif defined(__GNUC__)
+#define	__SYNC_ATOMICS
+#else
+#error "pmatomic.h does not support your compiler"
+#endif
+
+/*
+ * 7.17.1 Atomic lock-free macros.
+ */
+#if 0
+
+#ifdef __GCC_ATOMIC_BOOL_LOCK_FREE
+#define	ATOMIC_BOOL_LOCK_FREE		__GCC_ATOMIC_BOOL_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_CHAR_LOCK_FREE
+#define	ATOMIC_CHAR_LOCK_FREE		__GCC_ATOMIC_CHAR_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_CHAR16_T_LOCK_FREE
+#define	ATOMIC_CHAR16_T_LOCK_FREE	__GCC_ATOMIC_CHAR16_T_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_CHAR32_T_LOCK_FREE
+#define	ATOMIC_CHAR32_T_LOCK_FREE	__GCC_ATOMIC_CHAR32_T_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_WCHAR_T_LOCK_FREE
+#define	ATOMIC_WCHAR_T_LOCK_FREE	__GCC_ATOMIC_WCHAR_T_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_SHORT_LOCK_FREE
+#define	ATOMIC_SHORT_LOCK_FREE		__GCC_ATOMIC_SHORT_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_INT_LOCK_FREE
+#define	ATOMIC_INT_LOCK_FREE		__GCC_ATOMIC_INT_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_LONG_LOCK_FREE
+#define	ATOMIC_LONG_LOCK_FREE		__GCC_ATOMIC_LONG_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_LLONG_LOCK_FREE
+#define	ATOMIC_LLONG_LOCK_FREE		__GCC_ATOMIC_LLONG_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_POINTER_LOCK_FREE
+#define	ATOMIC_POINTER_LOCK_FREE	__GCC_ATOMIC_POINTER_LOCK_FREE
+#endif
+
+#endif
+
+/*
+ * 7.17.2 Initialization.
+ */
+#if 0
+
+#if defined(__CLANG_ATOMICS)
+#define	ATOMIC_VAR_INIT(value)		(value)
+#define	atomic_init(obj, value)		__c11_atomic_init(obj, value)
+#else
+#define	ATOMIC_VAR_INIT(value)		{ .__val = (value) }
+#define	atomic_init(obj, value)		((void)((obj)->__val = (value)))
+#endif
+
+#endif
+
+/*
+ * Clang and recent GCC both provide predefined macros for the memory
+ * orderings.  If we are using a compiler that doesn't define them, use the
+ * clang values - these will be ignored in the fallback path.
+ */
+
+#ifndef __ATOMIC_RELAXED
+#define __ATOMIC_RELAXED		0
+#endif
+#ifndef __ATOMIC_CONSUME
+#define __ATOMIC_CONSUME		1
+#endif
+#ifndef __ATOMIC_ACQUIRE
+#define __ATOMIC_ACQUIRE		2
+#endif
+#ifndef __ATOMIC_RELEASE
+#define __ATOMIC_RELEASE		3
+#endif
+#ifndef __ATOMIC_ACQ_REL
+#define __ATOMIC_ACQ_REL		4
+#endif
+#ifndef __ATOMIC_SEQ_CST
+#define __ATOMIC_SEQ_CST		5
+#endif
+
+/*
+ * 7.17.3 Order and consistency.
+ *
+ * The memory_order_* constants that denote the barrier behaviour of the
+ * atomic operations.
+ */
+
+typedef enum {
+	memory_order_relaxed = __ATOMIC_RELAXED,
+	memory_order_consume = __ATOMIC_CONSUME,
+	memory_order_acquire = __ATOMIC_ACQUIRE,
+	memory_order_release = __ATOMIC_RELEASE,
+	memory_order_acq_rel = __ATOMIC_ACQ_REL,
+	memory_order_seq_cst = __ATOMIC_SEQ_CST
+} memory_order;
+
+/*
+ * 7.17.4 Fences.
+ */
+
+static __inline void
+atomic_thread_fence(memory_order __order __attribute__((__unused__)))
+{
+
+#ifdef __CLANG_ATOMICS
+	__c11_atomic_thread_fence(__order);
+#elif defined(__GNUC_ATOMICS)
+	__atomic_thread_fence(__order);
+#else
+	__sync_synchronize();
+#endif
+}
+
+static __inline void
+atomic_signal_fence(memory_order __order __attribute__((__unused__)))
+{
+
+#ifdef __CLANG_ATOMICS
+	__c11_atomic_signal_fence(__order);
+#elif defined(__GNUC_ATOMICS)
+	__atomic_signal_fence(__order);
+#else
+	__asm volatile ("" ::: "memory");
+#endif
+}
+
+/*
+ * 7.17.5 Lock-free property.
+ */
+#if 0
+
+#if defined(_KERNEL)
+/* Atomics in kernelspace are always lock-free. */
+#define	atomic_is_lock_free(obj) \
+	((void)(obj), (_Bool)1)
+#elif defined(__CLANG_ATOMICS)
+#define	atomic_is_lock_free(obj) \
+	__atomic_is_lock_free(sizeof(*(obj)), obj)
+#elif defined(__GNUC_ATOMICS)
+#define	atomic_is_lock_free(obj) \
+	__atomic_is_lock_free(sizeof((obj)->__val), &(obj)->__val)
+#else
+#define	atomic_is_lock_free(obj) \
+	((void)(obj), sizeof((obj)->__val) <= sizeof(void *))
+#endif
+
+#endif
+
+/*
+ * 7.17.6 Atomic integer types.
+ */
+#if 0
+
+typedef _Atomic(_Bool)			atomic_bool;
+typedef _Atomic(char)			atomic_char;
+typedef _Atomic(signed char)		atomic_schar;
+typedef _Atomic(unsigned char)		atomic_uchar;
+typedef _Atomic(short)			atomic_short;
+typedef _Atomic(unsigned short)		atomic_ushort;
+typedef _Atomic(int)			atomic_int;
+typedef _Atomic(unsigned int)		atomic_uint;
+typedef _Atomic(long)			atomic_long;
+typedef _Atomic(unsigned long)		atomic_ulong;
+typedef _Atomic(long long)		atomic_llong;
+typedef _Atomic(unsigned long long)	atomic_ullong;
+typedef _Atomic(__char16_t)		atomic_char16_t;
+typedef _Atomic(__char32_t)		atomic_char32_t;
+typedef _Atomic(___wchar_t)		atomic_wchar_t;
+typedef _Atomic(__int_least8_t)		atomic_int_least8_t;
+typedef _Atomic(__uint_least8_t)	atomic_uint_least8_t;
+typedef _Atomic(__int_least16_t)	atomic_int_least16_t;
+typedef _Atomic(__uint_least16_t)	atomic_uint_least16_t;
+typedef _Atomic(__int_least32_t)	atomic_int_least32_t;
+typedef _Atomic(__uint_least32_t)	atomic_uint_least32_t;
+typedef _Atomic(__int_least64_t)	atomic_int_least64_t;
+typedef _Atomic(__uint_least64_t)	atomic_uint_least64_t;
+typedef _Atomic(__int_fast8_t)		atomic_int_fast8_t;
+typedef _Atomic(__uint_fast8_t)		atomic_uint_fast8_t;
+typedef _Atomic(__int_fast16_t)		atomic_int_fast16_t;
+typedef _Atomic(__uint_fast16_t)	atomic_uint_fast16_t;
+typedef _Atomic(__int_fast32_t)		atomic_int_fast32_t;
+typedef _Atomic(__uint_fast32_t)	atomic_uint_fast32_t;
+typedef _Atomic(__int_fast64_t)		atomic_int_fast64_t;
+typedef _Atomic(__uint_fast64_t)	atomic_uint_fast64_t;
+typedef _Atomic(__intptr_t)		atomic_intptr_t;
+typedef _Atomic(__uintptr_t)		atomic_uintptr_t;
+typedef _Atomic(__size_t)		atomic_size_t;
+typedef _Atomic(__ptrdiff_t)		atomic_ptrdiff_t;
+typedef _Atomic(__intmax_t)		atomic_intmax_t;
+typedef _Atomic(__uintmax_t)		atomic_uintmax_t;
+
+#endif
+
+/*
+ * 7.17.7 Operations on atomic types.
+ */
+
+/*
+ * Compiler-specific operations.
+ */
+
+#if defined(__CLANG_ATOMICS)
+#define	atomic_compare_exchange_strong_explicit(object, expected,	\
+    desired, success, failure)						\
+	__c11_atomic_compare_exchange_strong(object, expected, desired,	\
+	    success, failure)
+#define	atomic_compare_exchange_weak_explicit(object, expected,		\
+    desired, success, failure)						\
+	__c11_atomic_compare_exchange_weak(object, expected, desired,	\
+	    success, failure)
+#define	atomic_exchange_explicit(object, desired, order)		\
+	__c11_atomic_exchange(object, desired, order)
+#define	atomic_fetch_add_explicit(object, operand, order)		\
+	__c11_atomic_fetch_add(object, operand, order)
+#define	atomic_fetch_and_explicit(object, operand, order)		\
+	__c11_atomic_fetch_and(object, operand, order)
+#define	atomic_fetch_or_explicit(object, operand, order)		\
+	__c11_atomic_fetch_or(object, operand, order)
+#define	atomic_fetch_sub_explicit(object, operand, order)		\
+	__c11_atomic_fetch_sub(object, operand, order)
+#define	atomic_fetch_xor_explicit(object, operand, order)		\
+	__c11_atomic_fetch_xor(object, operand, order)
+#define	atomic_load_explicit(object, order)				\
+	__c11_atomic_load(object, order)
+#define	atomic_store_explicit(object, desired, order)			\
+	__c11_atomic_store(object, desired, order)
+#elif defined(__GNUC_ATOMICS)
+#define	atomic_compare_exchange_strong_explicit(object, expected,	\
+    desired, success, failure)						\
+	__atomic_compare_exchange_n(&(object)->__val, expected,		\
+	    desired, 0, success, failure)
+#define	atomic_compare_exchange_weak_explicit(object, expected,		\
+    desired, success, failure)						\
+	__atomic_compare_exchange_n(&(object)->__val, expected,		\
+	    desired, 1, success, failure)
+#define	atomic_exchange_explicit(object, desired, order)		\
+	__atomic_exchange_n(&(object)->__val, desired, order)
+#define	atomic_fetch_add_explicit(object, operand, order)		\
+	__atomic_fetch_add(&(object)->__val, operand, order)
+#define	atomic_fetch_and_explicit(object, operand, order)		\
+	__atomic_fetch_and(&(object)->__val, operand, order)
+#define	atomic_fetch_or_explicit(object, operand, order)		\
+	__atomic_fetch_or(&(object)->__val, operand, order)
+#define	atomic_fetch_sub_explicit(object, operand, order)		\
+	__atomic_fetch_sub(&(object)->__val, operand, order)
+#define	atomic_fetch_xor_explicit(object, operand, order)		\
+	__atomic_fetch_xor(&(object)->__val, operand, order)
+#define	atomic_load_explicit(object, order)				\
+	__atomic_load_n(&(object)->__val, order)
+#define	atomic_store_explicit(object, desired, order)			\
+	__atomic_store_n(&(object)->__val, desired, order)
+#else
+#define	__atomic_apply_stride(object, operand) \
+	(((__typeof__((object)->__val))0) + (operand))
+#define	atomic_compare_exchange_strong_explicit(object, expected,	\
+    desired, success, failure)	__extension__ ({			\
+	__typeof__(expected) __ep = (expected);				\
+	__typeof__(*__ep) __e = *__ep;					\
+	(void)(success); (void)(failure);				\
+	(_Bool)((*__ep = __sync_val_compare_and_swap(&(object)->__val,	\
+	    __e, desired)) == __e);					\
+})
+#define	atomic_compare_exchange_weak_explicit(object, expected,		\
+    desired, success, failure)						\
+	atomic_compare_exchange_strong_explicit(object, expected,	\
+		desired, success, failure)
+#if __has_builtin(__sync_swap)
+/* Clang provides a full-barrier atomic exchange - use it if available. */
+#define	atomic_exchange_explicit(object, desired, order)		\
+	((void)(order), __sync_swap(&(object)->__val, desired))
+#else
+/*
+ * __sync_lock_test_and_set() is only an acquire barrier in theory (although in
+ * practice it is usually a full barrier) so we need an explicit barrier before
+ * it.
+ */
+#define	atomic_exchange_explicit(object, desired, order)		\
+__extension__ ({							\
+	__typeof__(object) __o = (object);				\
+	__typeof__(desired) __d = (desired);				\
+	(void)(order);							\
+	__sync_synchronize();						\
+	__sync_lock_test_and_set(&(__o)->__val, __d);			\
+})
+#endif
+#define	atomic_fetch_add_explicit(object, operand, order)		\
+	((void)(order), __sync_fetch_and_add(&(object)->__val,		\
+	    __atomic_apply_stride(object, operand)))
+#define	atomic_fetch_and_explicit(object, operand, order)		\
+	((void)(order), __sync_fetch_and_and(&(object)->__val, operand))
+#define	atomic_fetch_or_explicit(object, operand, order)		\
+	((void)(order), __sync_fetch_and_or(&(object)->__val, operand))
+#define	atomic_fetch_sub_explicit(object, operand, order)		\
+	((void)(order), __sync_fetch_and_sub(&(object)->__val,		\
+	    __atomic_apply_stride(object, operand)))
+#define	atomic_fetch_xor_explicit(object, operand, order)		\
+	((void)(order), __sync_fetch_and_xor(&(object)->__val, operand))
+#define	atomic_load_explicit(object, order)				\
+	((void)(order), __sync_fetch_and_add(&(object)->__val, 0))
+#define	atomic_store_explicit(object, desired, order)			\
+	((void)atomic_exchange_explicit(object, desired, order))
+#endif
+
+/*
+ * Convenience functions.
+ *
+ * Don't provide these in kernel space. In kernel space, we should be
+ * disciplined enough to always provide explicit barriers.
+ */
+
+#ifndef _KERNEL
+#define	atomic_compare_exchange_strong(object, expected, desired)	\
+	atomic_compare_exchange_strong_explicit(object, expected,	\
+	    desired, memory_order_seq_cst, memory_order_seq_cst)
+#define	atomic_compare_exchange_weak(object, expected, desired)		\
+	atomic_compare_exchange_weak_explicit(object, expected,		\
+	    desired, memory_order_seq_cst, memory_order_seq_cst)
+#define	atomic_exchange(object, desired)				\
+	atomic_exchange_explicit(object, desired, memory_order_seq_cst)
+#define	atomic_fetch_add(object, operand)				\
+	atomic_fetch_add_explicit(object, operand, memory_order_seq_cst)
+#define	atomic_fetch_and(object, operand)				\
+	atomic_fetch_and_explicit(object, operand, memory_order_seq_cst)
+#define	atomic_fetch_or(object, operand)				\
+	atomic_fetch_or_explicit(object, operand, memory_order_seq_cst)
+#define	atomic_fetch_sub(object, operand)				\
+	atomic_fetch_sub_explicit(object, operand, memory_order_seq_cst)
+#define	atomic_fetch_xor(object, operand)				\
+	atomic_fetch_xor_explicit(object, operand, memory_order_seq_cst)
+#define	atomic_load(object)						\
+	atomic_load_explicit(object, memory_order_seq_cst)
+#define	atomic_store(object, desired)					\
+	atomic_store_explicit(object, desired, memory_order_seq_cst)
+#endif /* !_KERNEL */
+
+/*
+ * 7.17.8 Atomic flag type and operations.
+ *
+ * XXX: Assume atomic_bool can be used as an atomic_flag. Is there some
+ * kind of compiler built-in type we could use?
+ */
+#if 0
+
+typedef struct {
+	atomic_bool	__flag;
+} atomic_flag;
+
+#define	ATOMIC_FLAG_INIT		{ ATOMIC_VAR_INIT(0) }
+
+static __inline _Bool
+atomic_flag_test_and_set_explicit(volatile atomic_flag *__object,
+    memory_order __order)
+{
+	return (atomic_exchange_explicit(&__object->__flag, 1, __order));
+}
+
+static __inline void
+atomic_flag_clear_explicit(volatile atomic_flag *__object, memory_order __order)
+{
+
+	atomic_store_explicit(&__object->__flag, 0, __order);
+}
+
+#ifndef _KERNEL
+static __inline _Bool
+atomic_flag_test_and_set(volatile atomic_flag *__object)
+{
+
+	return (atomic_flag_test_and_set_explicit(__object,
+	    memory_order_seq_cst));
+}
+
+static __inline void
+atomic_flag_clear(volatile atomic_flag *__object)
+{
+
+	atomic_flag_clear_explicit(__object, memory_order_seq_cst);
+}
+#endif /* !_KERNEL */
+
+#endif
+
+#endif /* !_STDATOMIC_H_ */
diff --git a/third_party/pmatomic/stdatomic.h b/third_party/pmatomic/stdatomic.h
new file mode 100644
index 0000000000000000000000000000000000000000..bbab5b35df1141991c360899931224c569c37871
--- /dev/null
+++ b/third_party/pmatomic/stdatomic.h
@@ -0,0 +1,411 @@
+/*-
+ * Copyright (c) 2011 Ed Schouten <ed@FreeBSD.org>
+ *                    David Chisnall <theraven@FreeBSD.org>
+ * All rights reserved.
+ *
+ * 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 THE AUTHOR AND CONTRIBUTORS ``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 THE AUTHOR 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.
+ *
+ * $FreeBSD: releng/10.1/sys/sys/stdatomic.h 264496 2014-04-15 09:41:52Z tijl $
+ */
+
+#ifndef _STDATOMIC_H_
+#define	_STDATOMIC_H_
+
+#include <sys/cdefs.h>
+#include <sys/_types.h>
+
+#if __has_extension(c_atomic) || __has_extension(cxx_atomic)
+#define	__CLANG_ATOMICS
+#elif __GNUC_PREREQ__(4, 7)
+#define	__GNUC_ATOMICS
+#elif defined(__GNUC__)
+#define	__SYNC_ATOMICS
+#else
+#error "stdatomic.h does not support your compiler"
+#endif
+
+/*
+ * 7.17.1 Atomic lock-free macros.
+ */
+
+#ifdef __GCC_ATOMIC_BOOL_LOCK_FREE
+#define	ATOMIC_BOOL_LOCK_FREE		__GCC_ATOMIC_BOOL_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_CHAR_LOCK_FREE
+#define	ATOMIC_CHAR_LOCK_FREE		__GCC_ATOMIC_CHAR_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_CHAR16_T_LOCK_FREE
+#define	ATOMIC_CHAR16_T_LOCK_FREE	__GCC_ATOMIC_CHAR16_T_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_CHAR32_T_LOCK_FREE
+#define	ATOMIC_CHAR32_T_LOCK_FREE	__GCC_ATOMIC_CHAR32_T_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_WCHAR_T_LOCK_FREE
+#define	ATOMIC_WCHAR_T_LOCK_FREE	__GCC_ATOMIC_WCHAR_T_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_SHORT_LOCK_FREE
+#define	ATOMIC_SHORT_LOCK_FREE		__GCC_ATOMIC_SHORT_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_INT_LOCK_FREE
+#define	ATOMIC_INT_LOCK_FREE		__GCC_ATOMIC_INT_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_LONG_LOCK_FREE
+#define	ATOMIC_LONG_LOCK_FREE		__GCC_ATOMIC_LONG_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_LLONG_LOCK_FREE
+#define	ATOMIC_LLONG_LOCK_FREE		__GCC_ATOMIC_LLONG_LOCK_FREE
+#endif
+#ifdef __GCC_ATOMIC_POINTER_LOCK_FREE
+#define	ATOMIC_POINTER_LOCK_FREE	__GCC_ATOMIC_POINTER_LOCK_FREE
+#endif
+
+/*
+ * 7.17.2 Initialization.
+ */
+
+#if defined(__CLANG_ATOMICS)
+#define	ATOMIC_VAR_INIT(value)		(value)
+#define	atomic_init(obj, value)		__c11_atomic_init(obj, value)
+#else
+#define	ATOMIC_VAR_INIT(value)		{ .__val = (value) }
+#define	atomic_init(obj, value)		((void)((obj)->__val = (value)))
+#endif
+
+/*
+ * Clang and recent GCC both provide predefined macros for the memory
+ * orderings.  If we are using a compiler that doesn't define them, use the
+ * clang values - these will be ignored in the fallback path.
+ */
+
+#ifndef __ATOMIC_RELAXED
+#define __ATOMIC_RELAXED		0
+#endif
+#ifndef __ATOMIC_CONSUME
+#define __ATOMIC_CONSUME		1
+#endif
+#ifndef __ATOMIC_ACQUIRE
+#define __ATOMIC_ACQUIRE		2
+#endif
+#ifndef __ATOMIC_RELEASE
+#define __ATOMIC_RELEASE		3
+#endif
+#ifndef __ATOMIC_ACQ_REL
+#define __ATOMIC_ACQ_REL		4
+#endif
+#ifndef __ATOMIC_SEQ_CST
+#define __ATOMIC_SEQ_CST		5
+#endif
+
+/*
+ * 7.17.3 Order and consistency.
+ *
+ * The memory_order_* constants that denote the barrier behaviour of the
+ * atomic operations.
+ */
+
+typedef enum {
+	memory_order_relaxed = __ATOMIC_RELAXED,
+	memory_order_consume = __ATOMIC_CONSUME,
+	memory_order_acquire = __ATOMIC_ACQUIRE,
+	memory_order_release = __ATOMIC_RELEASE,
+	memory_order_acq_rel = __ATOMIC_ACQ_REL,
+	memory_order_seq_cst = __ATOMIC_SEQ_CST
+} memory_order;
+
+/*
+ * 7.17.4 Fences.
+ */
+
+static __inline void
+atomic_thread_fence(memory_order __order __unused)
+{
+
+#ifdef __CLANG_ATOMICS
+	__c11_atomic_thread_fence(__order);
+#elif defined(__GNUC_ATOMICS)
+	__atomic_thread_fence(__order);
+#else
+	__sync_synchronize();
+#endif
+}
+
+static __inline void
+atomic_signal_fence(memory_order __order __unused)
+{
+
+#ifdef __CLANG_ATOMICS
+	__c11_atomic_signal_fence(__order);
+#elif defined(__GNUC_ATOMICS)
+	__atomic_signal_fence(__order);
+#else
+	__asm volatile ("" ::: "memory");
+#endif
+}
+
+/*
+ * 7.17.5 Lock-free property.
+ */
+
+#if defined(_KERNEL)
+/* Atomics in kernelspace are always lock-free. */
+#define	atomic_is_lock_free(obj) \
+	((void)(obj), (_Bool)1)
+#elif defined(__CLANG_ATOMICS)
+#define	atomic_is_lock_free(obj) \
+	__atomic_is_lock_free(sizeof(*(obj)), obj)
+#elif defined(__GNUC_ATOMICS)
+#define	atomic_is_lock_free(obj) \
+	__atomic_is_lock_free(sizeof((obj)->__val), &(obj)->__val)
+#else
+#define	atomic_is_lock_free(obj) \
+	((void)(obj), sizeof((obj)->__val) <= sizeof(void *))
+#endif
+
+/*
+ * 7.17.6 Atomic integer types.
+ */
+
+typedef _Atomic(_Bool)			atomic_bool;
+typedef _Atomic(char)			atomic_char;
+typedef _Atomic(signed char)		atomic_schar;
+typedef _Atomic(unsigned char)		atomic_uchar;
+typedef _Atomic(short)			atomic_short;
+typedef _Atomic(unsigned short)		atomic_ushort;
+typedef _Atomic(int)			atomic_int;
+typedef _Atomic(unsigned int)		atomic_uint;
+typedef _Atomic(long)			atomic_long;
+typedef _Atomic(unsigned long)		atomic_ulong;
+typedef _Atomic(long long)		atomic_llong;
+typedef _Atomic(unsigned long long)	atomic_ullong;
+typedef _Atomic(__char16_t)		atomic_char16_t;
+typedef _Atomic(__char32_t)		atomic_char32_t;
+typedef _Atomic(___wchar_t)		atomic_wchar_t;
+typedef _Atomic(__int_least8_t)		atomic_int_least8_t;
+typedef _Atomic(__uint_least8_t)	atomic_uint_least8_t;
+typedef _Atomic(__int_least16_t)	atomic_int_least16_t;
+typedef _Atomic(__uint_least16_t)	atomic_uint_least16_t;
+typedef _Atomic(__int_least32_t)	atomic_int_least32_t;
+typedef _Atomic(__uint_least32_t)	atomic_uint_least32_t;
+typedef _Atomic(__int_least64_t)	atomic_int_least64_t;
+typedef _Atomic(__uint_least64_t)	atomic_uint_least64_t;
+typedef _Atomic(__int_fast8_t)		atomic_int_fast8_t;
+typedef _Atomic(__uint_fast8_t)		atomic_uint_fast8_t;
+typedef _Atomic(__int_fast16_t)		atomic_int_fast16_t;
+typedef _Atomic(__uint_fast16_t)	atomic_uint_fast16_t;
+typedef _Atomic(__int_fast32_t)		atomic_int_fast32_t;
+typedef _Atomic(__uint_fast32_t)	atomic_uint_fast32_t;
+typedef _Atomic(__int_fast64_t)		atomic_int_fast64_t;
+typedef _Atomic(__uint_fast64_t)	atomic_uint_fast64_t;
+typedef _Atomic(__intptr_t)		atomic_intptr_t;
+typedef _Atomic(__uintptr_t)		atomic_uintptr_t;
+typedef _Atomic(__size_t)		atomic_size_t;
+typedef _Atomic(__ptrdiff_t)		atomic_ptrdiff_t;
+typedef _Atomic(__intmax_t)		atomic_intmax_t;
+typedef _Atomic(__uintmax_t)		atomic_uintmax_t;
+
+/*
+ * 7.17.7 Operations on atomic types.
+ */
+
+/*
+ * Compiler-specific operations.
+ */
+
+#if defined(__CLANG_ATOMICS)
+#define	atomic_compare_exchange_strong_explicit(object, expected,	\
+    desired, success, failure)						\
+	__c11_atomic_compare_exchange_strong(object, expected, desired,	\
+	    success, failure)
+#define	atomic_compare_exchange_weak_explicit(object, expected,		\
+    desired, success, failure)						\
+	__c11_atomic_compare_exchange_weak(object, expected, desired,	\
+	    success, failure)
+#define	atomic_exchange_explicit(object, desired, order)		\
+	__c11_atomic_exchange(object, desired, order)
+#define	atomic_fetch_add_explicit(object, operand, order)		\
+	__c11_atomic_fetch_add(object, operand, order)
+#define	atomic_fetch_and_explicit(object, operand, order)		\
+	__c11_atomic_fetch_and(object, operand, order)
+#define	atomic_fetch_or_explicit(object, operand, order)		\
+	__c11_atomic_fetch_or(object, operand, order)
+#define	atomic_fetch_sub_explicit(object, operand, order)		\
+	__c11_atomic_fetch_sub(object, operand, order)
+#define	atomic_fetch_xor_explicit(object, operand, order)		\
+	__c11_atomic_fetch_xor(object, operand, order)
+#define	atomic_load_explicit(object, order)				\
+	__c11_atomic_load(object, order)
+#define	atomic_store_explicit(object, desired, order)			\
+	__c11_atomic_store(object, desired, order)
+#elif defined(__GNUC_ATOMICS)
+#define	atomic_compare_exchange_strong_explicit(object, expected,	\
+    desired, success, failure)						\
+	__atomic_compare_exchange_n(&(object)->__val, expected,		\
+	    desired, 0, success, failure)
+#define	atomic_compare_exchange_weak_explicit(object, expected,		\
+    desired, success, failure)						\
+	__atomic_compare_exchange_n(&(object)->__val, expected,		\
+	    desired, 1, success, failure)
+#define	atomic_exchange_explicit(object, desired, order)		\
+	__atomic_exchange_n(&(object)->__val, desired, order)
+#define	atomic_fetch_add_explicit(object, operand, order)		\
+	__atomic_fetch_add(&(object)->__val, operand, order)
+#define	atomic_fetch_and_explicit(object, operand, order)		\
+	__atomic_fetch_and(&(object)->__val, operand, order)
+#define	atomic_fetch_or_explicit(object, operand, order)		\
+	__atomic_fetch_or(&(object)->__val, operand, order)
+#define	atomic_fetch_sub_explicit(object, operand, order)		\
+	__atomic_fetch_sub(&(object)->__val, operand, order)
+#define	atomic_fetch_xor_explicit(object, operand, order)		\
+	__atomic_fetch_xor(&(object)->__val, operand, order)
+#define	atomic_load_explicit(object, order)				\
+	__atomic_load_n(&(object)->__val, order)
+#define	atomic_store_explicit(object, desired, order)			\
+	__atomic_store_n(&(object)->__val, desired, order)
+#else
+#define	__atomic_apply_stride(object, operand) \
+	(((__typeof__((object)->__val))0) + (operand))
+#define	atomic_compare_exchange_strong_explicit(object, expected,	\
+    desired, success, failure)	__extension__ ({			\
+	__typeof__(expected) __ep = (expected);				\
+	__typeof__(*__ep) __e = *__ep;					\
+	(void)(success); (void)(failure);				\
+	(_Bool)((*__ep = __sync_val_compare_and_swap(&(object)->__val,	\
+	    __e, desired)) == __e);					\
+})
+#define	atomic_compare_exchange_weak_explicit(object, expected,		\
+    desired, success, failure)						\
+	atomic_compare_exchange_strong_explicit(object, expected,	\
+		desired, success, failure)
+#if __has_builtin(__sync_swap)
+/* Clang provides a full-barrier atomic exchange - use it if available. */
+#define	atomic_exchange_explicit(object, desired, order)		\
+	((void)(order), __sync_swap(&(object)->__val, desired))
+#else
+/*
+ * __sync_lock_test_and_set() is only an acquire barrier in theory (although in
+ * practice it is usually a full barrier) so we need an explicit barrier before
+ * it.
+ */
+#define	atomic_exchange_explicit(object, desired, order)		\
+__extension__ ({							\
+	__typeof__(object) __o = (object);				\
+	__typeof__(desired) __d = (desired);				\
+	(void)(order);							\
+	__sync_synchronize();						\
+	__sync_lock_test_and_set(&(__o)->__val, __d);			\
+})
+#endif
+#define	atomic_fetch_add_explicit(object, operand, order)		\
+	((void)(order), __sync_fetch_and_add(&(object)->__val,		\
+	    __atomic_apply_stride(object, operand)))
+#define	atomic_fetch_and_explicit(object, operand, order)		\
+	((void)(order), __sync_fetch_and_and(&(object)->__val, operand))
+#define	atomic_fetch_or_explicit(object, operand, order)		\
+	((void)(order), __sync_fetch_and_or(&(object)->__val, operand))
+#define	atomic_fetch_sub_explicit(object, operand, order)		\
+	((void)(order), __sync_fetch_and_sub(&(object)->__val,		\
+	    __atomic_apply_stride(object, operand)))
+#define	atomic_fetch_xor_explicit(object, operand, order)		\
+	((void)(order), __sync_fetch_and_xor(&(object)->__val, operand))
+#define	atomic_load_explicit(object, order)				\
+	((void)(order), __sync_fetch_and_add(&(object)->__val, 0))
+#define	atomic_store_explicit(object, desired, order)			\
+	((void)atomic_exchange_explicit(object, desired, order))
+#endif
+
+/*
+ * Convenience functions.
+ *
+ * Don't provide these in kernel space. In kernel space, we should be
+ * disciplined enough to always provide explicit barriers.
+ */
+
+#ifndef _KERNEL
+#define	atomic_compare_exchange_strong(object, expected, desired)	\
+	atomic_compare_exchange_strong_explicit(object, expected,	\
+	    desired, memory_order_seq_cst, memory_order_seq_cst)
+#define	atomic_compare_exchange_weak(object, expected, desired)		\
+	atomic_compare_exchange_weak_explicit(object, expected,		\
+	    desired, memory_order_seq_cst, memory_order_seq_cst)
+#define	atomic_exchange(object, desired)				\
+	atomic_exchange_explicit(object, desired, memory_order_seq_cst)
+#define	atomic_fetch_add(object, operand)				\
+	atomic_fetch_add_explicit(object, operand, memory_order_seq_cst)
+#define	atomic_fetch_and(object, operand)				\
+	atomic_fetch_and_explicit(object, operand, memory_order_seq_cst)
+#define	atomic_fetch_or(object, operand)				\
+	atomic_fetch_or_explicit(object, operand, memory_order_seq_cst)
+#define	atomic_fetch_sub(object, operand)				\
+	atomic_fetch_sub_explicit(object, operand, memory_order_seq_cst)
+#define	atomic_fetch_xor(object, operand)				\
+	atomic_fetch_xor_explicit(object, operand, memory_order_seq_cst)
+#define	atomic_load(object)						\
+	atomic_load_explicit(object, memory_order_seq_cst)
+#define	atomic_store(object, desired)					\
+	atomic_store_explicit(object, desired, memory_order_seq_cst)
+#endif /* !_KERNEL */
+
+/*
+ * 7.17.8 Atomic flag type and operations.
+ *
+ * XXX: Assume atomic_bool can be used as an atomic_flag. Is there some
+ * kind of compiler built-in type we could use?
+ */
+
+typedef struct {
+	atomic_bool	__flag;
+} atomic_flag;
+
+#define	ATOMIC_FLAG_INIT		{ ATOMIC_VAR_INIT(0) }
+
+static __inline _Bool
+atomic_flag_test_and_set_explicit(volatile atomic_flag *__object,
+    memory_order __order)
+{
+	return (atomic_exchange_explicit(&__object->__flag, 1, __order));
+}
+
+static __inline void
+atomic_flag_clear_explicit(volatile atomic_flag *__object, memory_order __order)
+{
+
+	atomic_store_explicit(&__object->__flag, 0, __order);
+}
+
+#ifndef _KERNEL
+static __inline _Bool
+atomic_flag_test_and_set(volatile atomic_flag *__object)
+{
+
+	return (atomic_flag_test_and_set_explicit(__object,
+	    memory_order_seq_cst));
+}
+
+static __inline void
+atomic_flag_clear(volatile atomic_flag *__object)
+{
+
+	atomic_flag_clear_explicit(__object, memory_order_seq_cst);
+}
+#endif /* !_KERNEL */
+
+#endif /* !_STDATOMIC_H_ */
diff --git a/third_party/sophia b/third_party/sophia
index d5928323bdaada3fd9118ddea66714ea04b4ebc7..1dc584e3c6bc425fa4f9dd8afdaca59a482a13cd 160000
--- a/third_party/sophia
+++ b/third_party/sophia
@@ -1 +1 @@
-Subproject commit d5928323bdaada3fd9118ddea66714ea04b4ebc7
+Subproject commit 1dc584e3c6bc425fa4f9dd8afdaca59a482a13cd