{"id":20564335,"url":"https://github.com/tarantool/crud","last_synced_at":"2025-08-21T03:32:55.093Z","repository":{"id":37807586,"uuid":"297347284","full_name":"tarantool/crud","owner":"tarantool","description":"Easy assess to data stored in vshard cluster","archived":false,"fork":false,"pushed_at":"2024-08-28T10:57:56.000Z","size":1237,"stargazers_count":41,"open_issues_count":56,"forks_count":14,"subscribers_count":21,"default_branch":"master","last_synced_at":"2025-04-08T03:35:35.530Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tarantool.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS","dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-09-21T13:25:37.000Z","updated_at":"2025-03-20T18:50:31.000Z","dependencies_parsed_at":"2023-02-12T23:30:49.055Z","dependency_job_id":"22638d4a-7c15-4435-b0c8-742e0a56a969","html_url":"https://github.com/tarantool/crud","commit_stats":null,"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"purl":"pkg:github/tarantool/crud","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarantool%2Fcrud","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarantool%2Fcrud/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarantool%2Fcrud/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarantool%2Fcrud/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tarantool","download_url":"https://codeload.github.com/tarantool/crud/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarantool%2Fcrud/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271420541,"owners_count":24756589,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-21T02:00:08.990Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-16T04:25:48.571Z","updated_at":"2025-08-21T03:32:54.484Z","avatar_url":"https://github.com/tarantool.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CRUD\n\n[![Run static analysis](https://github.com/tarantool/crud/actions/workflows/check_on_push.yaml/badge.svg)](https://github.com/tarantool/crud/actions/workflows/check_on_push.yaml)\n[![Run tests](https://github.com/tarantool/crud/actions/workflows/test_on_push.yaml/badge.svg)](https://github.com/tarantool/crud/actions/workflows/test_on_push.yaml)\n[![Coverage Status](https://coveralls.io/repos/github/tarantool/crud/badge.svg?branch=master)](https://coveralls.io/github/tarantool/crud?branch=master)\n\nThe `CRUD` module allows to perform CRUD operations on the cluster.\nIt also provides the `crud-storage` and `crud-router` roles for\n[Tarantool Cartridge](https://github.com/tarantool/cartridge).\n\n## Table of Contents\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n- [Quickstart](#quickstart)\n  - [Install](#install)\n    - [Manual install](#manual-install)\n    - [Application dependency](#application-dependency)\n    - [Repository clone](#repository-clone)\n  - [Usage](#usage)\n  - [Sandbox](#sandbox)\n- [API](#api)\n  - [Package info](#package-info)\n  - [Insert](#insert)\n  - [Insert many](#insert-many)\n  - [Get](#get)\n  - [Update](#update)\n  - [Delete](#delete)\n  - [Replace](#replace)\n  - [Replace many](#replace-many)\n  - [Upsert](#upsert)\n  - [Upsert many](#upsert-many)\n  - [Select](#select)\n    - [Select conditions](#select-conditions)\n  - [Pairs](#pairs)\n  - [Min and max](#min-and-max)\n  - [Cut extra rows](#cut-extra-rows)\n  - [Cut extra objects](#cut-extra-objects)\n  - [Truncate](#truncate)\n  - [Len](#len)\n  - [Storage info](#storage-info)\n  - [Count](#count)\n  - [Call options for crud methods](#call-options-for-crud-methods)\n  - [Statistics](#statistics)\n  - [Read view](#read-view)\n    - [Creating a read view](#creating-a-read-view)\n    - [Closing a read view](#closing-a-read-view)\n    - [Read view select](#read-view-select)\n      - [Read view select conditions](#read-view-select-conditions)\n    - [Read view pairs](#read-view-pairs)\n  - [Schema](#schema)\n- [Tarantool 3 roles](#tarantool-3-roles)\n  - [Usage](#usage-1)\n- [Cartridge roles](#cartridge-roles)\n  - [Usage](#usage-2)\n- [License](#license)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n## Quickstart\n\nFirst, [install Tarantool](https://www.tarantool.io/en/download).\n\n### Install\n\n#### Manual install\n\nTo try `crud` in your application, you may install it manually fron web\nwith `tt rocks` rock management tool.\n\n```bash\ntt rocks install crud\n```\n\n#### Application dependency\n\nTo use crud in your application, set it as a rockspec dependency.\n\n```lua\npackage = 'myapp'\n\nversion = 'scm-1'\n\nsource  = {\n    url = '/dev/null',\n}\n\ndependencies = {\n    'tarantool \u003e= 3.1.0',\n    'crud == \u003cthe-latest-tag\u003e-1',\n}\n\nbuild = {\n    type = 'none';\n}\n```\n\n#### Repository clone\n\nYou can also clone the repository to explore crud and try it inside a sandbox.\n\n```bash\ngit clone https://github.com/tarantool/crud.git\ncd crud\ntt rocks make\n```\n\n### Usage\n\nFor Tarantool 3.x, enable crud roles on your application instances in a configuration\n(see [Tarantool 3 roles](#tarantool-3-roles) section).\nRoles support Tarantool 3.0.2, Tarantool 3.1.0 and newer.\nOlder versions are not supported due to\n[tarantool/tarantool#9643](https://github.com/tarantool/tarantool/issues/9643) and\n[tarantool/tarantool#9649](https://github.com/tarantool/tarantool/issues/9649)\nissues.\n\nFor Tarantool 1.10 and 2.x, add crud roles into dependencies of your roles\n(see [Cartridge roles](#cartridge-roles) section).\n\nFor Tarantool 1.10, 2.x and 3.x you can also manually call\nthe [crud initialization code](#api) on [VShard](https://github.com/tarantool/vshard)\nrouter and storage instances.\n\n### Sandbox\n\nThe repository provide a simple sandbox application with a test dataset on a single instance.\n\n```bash\n./doc/playground.lua\ntarantool\u003e crud.select('customers', {{'\u003c=', 'age', 35}}, {first = 10})\ntarantool\u003e crud.select('developers', nil, {first = 6})\n```\n\n## API\n\nThe CRUD operations should be called from router.\n\nAll VShard storages should call `crud.init_storage()` after\n`vshard.storage.cfg()` (or enable the `roles.crud-storage` role for Tarantool 3\nor the `crud-storage` role for Cartridge)\nfirst to initialize storage-side functions that are used to manipulate data\nacross the cluster. The storage-side functions have the same access\nas a user calling `crud.init_storage()`. Therefore, if `crud` do not have\nenough access to modify some space, then you need to give access to the user.\n\nYou can call `crud.init_storage{async = true}` to bootstrap procedures grants\nasynchronously. It is useful in case your application master instances may\nstart in ro mode (for example, if you use Tarantool 3.x). By default,\nasynchronous bootstrap is used for Tarantool 3.x and\nsynchronous bootstrap is used for Tarantool 1.10 and 2.x.\n\nAll VShard routers should call `crud.init_router()` after `vshard.router.cfg()`\n(or enable the `roles.crud-storage` role for Tarantool 3\nor the `crud-router` role for Cartridge) to make `crud` functions\ncallable via `net.box`. If a user is allowed to execute `crud` functions on\nthe router-side then the user does not need additional access on storages.\n\nYou can check out an example of the configuration for local development\n(a single instance that combines router and storage) in\n[playground.lua](./doc/playground.lua).\n\nAll operations return a table that contains rows (tuples) and metadata\n(space format).\nIt can be used to convert received tuples to objects via `crud.unflatten_rows` function.\n\nFor example:\n\n```lua\nres, err = crud.select('customers', nil, {first = 2})\nres\n---\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows:\n  - [1, 12477, 'Elizabeth', 12]\n  - [2, 21401, 'David', 33]\n...\ncrud.unflatten_rows(res.rows, res.metadata)\n---\n- - bucket_id: 12477\n    age: 12\n    name: Elizabeth\n    id: 1\n  - bucket_id: 21401\n    age: 33\n    name: David\n    id: 2\n...\n```\n\n**Notes:**\n\n* A space should have a format.\n\n**Sharding key and bucket id calculation**\n\n*Sharding key* is a set of tuple field values used for calculation *bucket ID*.\n*Sharding key definition* is a set of tuple field names that describe what\ntuple field should be a part of sharding key. *Bucket ID* determines which\nreplicaset stores certain data. Function that used for bucket ID calculation is\nnamed *sharding function*.\n\nBy default CRUD calculates bucket ID using primary key and a function\n`vshard.router.bucket_id_strcrc32(key)`, it happen automatically and doesn't\nrequire any actions from user side. However, for operations that accepts\ntuple/object bucket ID can be specified as tuple/object field as well as\n`opts.bucket_id` value.\n\nStarting from 0.10.0 users who don't want to use primary key as a sharding key\nmay set custom sharding key definition as a part of [DDL\nschema](https://github.com/tarantool/ddl#input-data-format) or insert manually\nto the space `_ddl_sharding_key` (for both cases consider a DDL module\ndocumentation). As soon as sharding key for a certain space is available in\n`_ddl_sharding_key` space CRUD will use it for bucket ID calculation\nautomatically. Note that CRUD methods `delete()`, `get()` and `update()`\nrequires that sharding key must be a part of primary key.\n\nStarting from 0.11.0 you can specify sharding function to calculate bucket_id\nwith sharding func definition as a part of\n[DDL schema](https://github.com/tarantool/ddl#input-data-format)\nor insert manually to the space `_ddl_sharding_func`.\n\nAutomatic sharding key and function reload is supported since version 0.11.0.\nVersion 0.11.0 contains critical bug that causes some CRUD methods to fail\nwith \"Sharding hash mismatch\" error if ddl is set and bucket_id is provided\nexplicitly ([#278](https://github.com/tarantool/crud/issues/278)). Please,\nupgrade to 0.11.1 instead.\n\nCRUD uses `strcrc32` as sharding function by default.\nThe reason why using of `strcrc32` is undesirable is that\nthis sharding function is not consistent for cdata numbers.\nIn particular, it returns 3 different values for normal Lua\nnumbers like 123, for `unsigned long long` cdata\n(like `123ULL`, or `ffi.cast('unsigned long long',\n123)`), and for `signed long long` cdata (like `123LL`, or\n`ffi.cast('long long', 123)`).\n\nWe cannot change default sharding function `strcrc32`\ndue to backward compatibility concerns, but please consider\nusing better alternatives for sharding function.\n`mpcrc32` is one of them.\n\nTable below describe what operations supports custom sharding key:\n\n| CRUD method                      | Sharding key support       |\n| -------------------------------- | -------------------------- |\n| `get()`                          | Yes                        |\n| `insert()` / `insert_object()`   | Yes                        |\n| `delete()`                       | Yes                        |\n| `replace()` / `replace_object()` | Yes                        |\n| `upsert()` / `upsert_object()`   | Yes                        |\n| `select()` / `pairs()`           | Yes                        |\n| `count()`                        | Yes                        |\n| `update()`                       | Yes                        |\n| `min()` / `max()`                | No (not required)          |\n| `cut_rows()` / `cut_objects()`   | No (not required)          |\n| `truncate()`                     | No (not required)          |\n| `len()`                          | No (not required)          |\n\nCurrent limitations for using custom sharding key:\n\n- No support of JSON path for sharding key, see\n  [#219](https://github.com/tarantool/crud/issues/219).\n- `primary_index_fieldno_map` is not cached, see\n  [#243](https://github.com/tarantool/crud/issues/243).\n\n### Package info\n\n```lua\ntarantool\u003e require('crud')._VERSION\n---\n- 1.1.0\n...\n```\n\nUse `_VERSION` handle to check installed module version.\nThe handle was introduced in `1.1.0`. If installed from master,\n`_VERSION` shows last tagged version.\n\n### Insert\n\n```lua\n-- Insert tuple\nlocal result, err = crud.insert(space_name, tuple, opts)\n-- Insert object\nlocal result, err = crud.insert_object(space_name, object, opts)\n```\n\nwhere:\n\n* `space_name` (`string`) - name of the space to insert an object\n* `tuple` / `object` (`table`) - tuple/object to insert\n* `opts`:\n  * `timeout` (`?number`) - `vshard.call` timeout and vshard master\n    discovery timeout (in seconds), default value is 2\n  * `bucket_id` (`?number|cdata`) - bucket ID\n  * `fields` (`?table`) - field names for getting only a subset of fields\n  * `vshard_router` (`?string|table`) - Cartridge vshard group name or\n    vshard router instance. Set this parameter if your space is not\n    a part of the default vshard cluster\n  * `skip_nullability_check_on_flatten` (`?boolean`) - option for\n    `insert_object` only. `false` by default. Set this parameter to\n    `true` if you want to allow setting null values to non-nullable\n    fields, which can be useful if non-nullable field value is generated by\n    [sequence](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_schema_sequence/create_index/).\n    **Warning**: there is no native support for sequences in sharded systems\n    since each replicaset has its own sequence. If sequence field is a part\n    of the sharding key (which is true by default), choosing the bucket id is\n    the sole responsibility of the developer\n  * `noreturn` (`?boolean`) - suppress successfully processed tuple\n    (first return value is `nil`). `false` by default\n  * `fetch_latest_metadata` (`?boolean`) - guarantees the\n    up-to-date metadata (space format) in first return value, otherwise\n    it may not take into account the latest migration of the data format.\n    Performance overhead is up to 15%. `false` by default\n\nReturns metadata and array contains one inserted row, error.\n\n**Example:**\n\n```lua\ncrud.insert('customers', {1, box.NULL, 'Elizabeth', 23})\n---\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows:\n  - [1, 477, 'Elizabeth', 23]\n...\ncrud.insert_object('customers', {\n    id = 2, name = 'Elizabeth', age = 24,\n})\n---\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows:\n  - [2, 401, 'Elizabeth', 24]\n...\n```\n\n### Insert many\n\n```lua\n-- Insert batch of tuples\nlocal result, err = crud.insert_many(space_name, tuples, opts)\n-- Insert batch of objects\nlocal result, err = crud.insert_object_many(space_name, objects, opts)\n```\n\nwhere:\n\n* `space_name` (`string`) - name of the space to insert an object\n* `tuples` / `objects` (`table`) - array of tuples/objects to insert (at least one)\n* `opts`:\n  * `timeout` (`?number`) - `vshard.call` timeout and vshard master\n    discovery timeout (in seconds), default value is 2\n  * `fields` (`?table`) - field names for getting only a subset of fields\n  * `stop_on_error` (`?boolean`) - stop on a first error and report error\n    regarding the failed operation and error about what tuples were not\n    performed, default is `false`\n  * `rollback_on_error` (`?boolean`) - any failed operation will lead to\n    rollback on a storage, where the operation is failed, report error\n    about what tuples were rollback, default is `false`\n  * `vshard_router` (`?string|table`) - Cartridge vshard group name or\n    vshard router instance. Set this parameter if your space is not\n    a part of the default vshard cluster\n  * `skip_nullability_check_on_flatten` (`?boolean`) - option for\n    `insert_object_many` only. `false` by default. Set this parameter to\n    `true` if you want to allow setting null values to non-nullable\n    fields, which can be useful if non-nullable field value is generated by\n    [sequence](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_schema_sequence/create_index/).\n    **Warning**: there is no native support for sequences in sharded systems\n    since each replicaset has its own sequence. If sequence field is a part\n    of the sharding key (which is true by default), choosing the bucket id is\n    the sole responsibility of the developer\n  * `noreturn` (`?boolean`) - suppress successfully processed tuples\n    (first return value is `nil`). `false` by default\n  * `fetch_latest_metadata` (`?boolean`) - guarantees the\n    up-to-date metadata (space format) in first return value, otherwise\n    it may not take into account the latest migration of the data format.\n    Performance overhead is up to 15%. `false` by default\n\nReturns metadata and array with inserted rows, array of errors.\nEach error object can contain field `operation_data`.\n\n`operation_data` field can contain:\n* tuple for which the error occurred;\n* object with an incorrect format;\n* tuple the operation on which was performed but\n  operation was rollback;\n* tuple the operation on which was not performed\n  because operation was stopped by error.\n\nRight now CRUD cannot provide batch insert with full consistency.\nCRUD offers batch insert with partial consistency. That means\nthat full consistency can be provided only on single replicaset\nusing `box` transactions.\n\n**Example:**\n\n```lua\ncrud.insert_many('customers', {\n  {1, box.NULL, 'Elizabeth', 23},\n  {2, box.NULL, 'Anastasia', 22},\n})\n---\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows:\n  - [1, 477, 'Elizabeth', 23]\n  - [2, 401, 'Anastasia', 22]\n...\ncrud.insert_object_many('customers', {\n    {id = 3, name = 'Elizabeth', age = 24},\n    {id = 10, name = 'Anastasia', age = 21},\n})\n---\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows:\n  - [3, 2804, 'Elizabeth', 24]\n  - [10, 569, 'Anastasia', 21]\n\n-- Partial success\nlocal res, errs = crud.insert_object_many('customers', {\n    {id = 22, name = 'Alex', age = 34},\n    {id = 3, name = 'Anastasia', age = 22},\n    {id = 5, name = 'Sergey', age = 25},\n})\n---\nres\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows:\n  - [5, 1172, 'Sergey', 25],\n  - [22, 655, 'Alex', 34],\n\n#errs                  -- 1\nerrs[1].class_name     -- BatchInsertError\nerrs[1].err            -- 'Duplicate key exists \u003c...\u003e'\nerrs[1].operation_data -- {3, 2804, 'Anastasia', 22}\n...\n\n-- Partial success with stop and rollback on error\n-- stop_on_error = true, rollback_on_error = true\n-- two error on one storage with rollback, inserts\n-- stop by error on this storage inserts before\n-- error are rollback\nlocal res, errs =  crud.insert_object_many('customers', {\n    {id = 6, name = 'Alex', age = 34},\n    {id = 92, name = 'Artur', age = 29},\n    {id = 3, name = 'Anastasia', age = 22},\n    {id = 4, name = 'Sergey', age = 25},\n    {id = 9, name = 'Anna', age = 30},\n    {id = 71, name = 'Oksana', age = 29},\n}, {\n    stop_on_error = true,\n    rollback_on_error  = true,\n})\n---\nres\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows:\n  - [4, 1161, 'Sergey', 25],\n  - [6, 1064, 'Alex', 34],\n#errs                  -- 4\nerrs[1].class_name     -- InsertManyError\nerrs[1].err            -- 'Duplicate key exists \u003c...\u003e'\nerrs[1].operation_data -- {3, 2804, 'Anastasia', 22}\n\nerrs[2].class_name     -- NotPerformedError\nerrs[2].err            -- 'Operation with tuple was not performed'\nerrs[2].operation_data -- {9, 1644, \"Anna\", 30}\n\nerrs[3].class_name     -- NotPerformedError\nerrs[3].err            -- 'Operation with tuple was not performed'\nerrs[3].operation_data -- {71, 1802, \"Oksana\", 29}\n\nerrs[4].class_name     -- NotPerformedError\nerrs[4].err            -- 'Operation with tuple was rollback'\nerrs[4].operation_data -- {92, 2040, \"Artur\", 29}\n```\n\n### Get\n\n```lua\nlocal result, err = crud.get(space_name, key, opts)\n```\n\nwhere:\n\n* `space_name` (`string`) - name of the space\n* `key` (`any`) - primary key value\n* `opts`:\n  * `fields` (`?table`) - field names for getting only a subset of fields\n  * `bucket_id` (`?number|cdata`) - bucket ID\n  * `timeout` (`?number`) - `vshard.call` timeout and vshard master\n    discovery timeout (in seconds), default value is 2\n  * `mode` (`?string`, `read` or `write`) - if `write` is specified then `get` is\n    performed on master, default value is `read`\n  * `prefer_replica` (`?boolean`) - if `true` then the preferred target is one of\n    the replicas\n  * `balance` (`?boolean`) - use replica according to vshard load balancing policy\n  * `vshard_router` (`?string|table`) - Cartridge vshard group name or\n    vshard router instance. Set this parameter if your space is not\n    a part of the default vshard cluster\n  * `fetch_latest_metadata` (`?boolean`) - guarantees the\n    up-to-date metadata (space format) in first return value, otherwise\n    it may not take into account the latest migration of the data format.\n    Performance overhead is up to 15%. `false` by default\n\nReturns metadata and array contains one row, error.\n\n**Example:**\n\n```lua\ncrud.get('customers', 1)\n---\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows:\n  - [1, 477, 'Elizabeth', 23]\n...\n```\n\n### Update\n\n```lua\nlocal result, err = crud.update(space_name, key, operations, opts)\n```\n\nwhere:\n\n* `space_name` (`string`) - name of the space\n* `key` (`any`) - primary key value\n* `operations` (`table`) - update [operations](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_space/update/)\n* `opts`:\n  * `timeout` (`?number`) - `vshard.call` timeout and vshard master\n    discovery timeout (in seconds), default value is 2\n  * `bucket_id` (`?number|cdata`) - bucket ID\n  * `fields` (`?table`) - field names for getting only a subset of fields\n  * `vshard_router` (`?string|table`) - Cartridge vshard group name or\n    vshard router instance. Set this parameter if your space is not\n    a part of the default vshard cluster\n  * `noreturn` (`?boolean`) - suppress successfully processed tuple\n    (first return value is `nil`). `false` by default\n  * `fetch_latest_metadata` (`?boolean`) - guarantees the\n    up-to-date metadata (space format) in first return value, otherwise\n    it may not take into account the latest migration of the data format.\n    Performance overhead is up to 15%. `false` by default\n\nReturns metadata and array contains one updated row, error.\n\n**Example:**\n\n```lua\ncrud.update('customers', 1, {{'+', 'age', 1}})\n---\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows:\n  - [1, 477, 'Elizabeth', 24]\n...\n```\n\n### Delete\n\n```lua\nlocal result, err = crud.delete(space_name, key, opts)\n```\n\nwhere:\n\n* `space_name` (`string`) - name of the space\n* `key` (`any`) - primary key value\n* `opts`:\n  * `timeout` (`?number`) - `vshard.call` timeout and vshard master\n    discovery timeout (in seconds), default value is 2\n  * `bucket_id` (`?number|cdata`) - bucket ID\n  * `fields` (`?table`) - field names for getting only a subset of fields\n  * `vshard_router` (`?string|table`) - Cartridge vshard group name or\n    vshard router instance. Set this parameter if your space is not\n    a part of the default vshard cluster\n  * `noreturn` (`?boolean`) - suppress successfully processed tuple\n    (first return value is `nil`). `false` by default\n  * `fetch_latest_metadata` (`?boolean`) - guarantees the\n    up-to-date metadata (space format) in first return value, otherwise\n    it may not take into account the latest migration of the data format.\n    Performance overhead is up to 15%. `false` by default\n\nReturns metadata and array contains one deleted row (empty for vinyl), error.\n\n**Example:**\n\n```lua\ncrud.delete('customers', 1)\n---\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows:\n  - [1, 477, 'Elizabeth', 24]\n```\n\n### Replace\n\n```lua\n-- Replace tuple\nlocal result, err = crud.replace(space_name, tuple, opts)\n-- Replace object\nlocal result, err = crud.replace_object(space_name, object, opts)\n```\n\nwhere:\n\n* `space_name` (`string`) - name of the space\n* `tuple` / `object` (`table`) - tuple/object to insert or replace exist one\n* `opts`:\n  * `timeout` (`?number`) - `vshard.call` timeout and vshard master\n    discovery timeout (in seconds), default value is 2\n  * `bucket_id` (`?number|cdata`) - bucket ID\n  * `fields` (`?table`) - field names for getting only a subset of fields\n  * `vshard_router` (`?string|table`) - Cartridge vshard group name or\n    vshard router instance. Set this parameter if your space is not\n    a part of the default vshard cluster\n  * `skip_nullability_check_on_flatten` (`?boolean`) - option for\n    `replace_object` only. `false` by default. Set this parameter to\n    `true` if you want to allow setting null values to non-nullable\n    fields, which can be useful if non-nullable field value is generated by\n    [sequence](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_schema_sequence/create_index/).\n    **Warning**: there is no native support for sequences in sharded systems\n    since each replicaset has its own sequence. If sequence field is a part\n    of the sharding key (which is true by default), choosing the bucket id is\n    the sole responsibility of the developer\n  * `noreturn` (`?boolean`) - suppress successfully processed tuple\n    (first return value is `nil`). `false` by default\n  * `fetch_latest_metadata` (`?boolean`) - guarantees the\n    up-to-date metadata (space format) in first return value, otherwise\n    it may not take into account the latest migration of the data format.\n    Performance overhead is up to 15%. `false` by default\n\nReturns inserted or replaced rows and metadata or nil with error.\n\n**Example:**\n\n```lua\ncrud.replace('customers', {1, box.NULL, 'Alice', 22})\n---\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows:\n  - [1, 477, 'Alice', 22]\n...\ncrud.replace_object('customers', {\n    id = 1, name = 'Alice', age = 22,\n})\n---\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows:\n  - [1, 477, 'Alice', 22]\n...\n```\n\n### Replace many\n\n```lua\n-- Replace batch of tuples\nlocal result, err = crud.replace_many(space_name, tuples, opts)\n-- Replace batch of objects\nlocal result, err = crud.replace_object_many(space_name, objects, opts)\n```\n\nwhere:\n\n* `space_name` (`string`) - name of the space to insert/replace an object\n* `tuples` / `objects` (`table`) - array of tuples/objects to replace (at least one)\n* `opts`:\n  * `timeout` (`?number`) - `vshard.call` timeout and vshard master\n    discovery timeout (in seconds), default value is 2\n  * `fields` (`?table`) - field names for getting only a subset of fields\n  * `stop_on_error` (`?boolean`) - stop on a first error and report error\n    regarding the failed operation and error about what tuples were not\n    performed, default is `false`\n  * `rollback_on_error` (`?boolean`) - any failed operation will lead to\n    rollback on a storage, where the operation is failed, report error\n    about what tuples were rollback, default is `false`\n  * `vshard_router` (`?string|table`) - Cartridge vshard group name or\n    vshard router instance. Set this parameter if your space is not\n    a part of the default vshard cluster\n  * `skip_nullability_check_on_flatten` (`?boolean`) - option for\n    `replace_object_many` only. `false` by default. Set this parameter to\n    `true` if you want to allow setting null values to non-nullable\n    fields, which can be useful if non-nullable field value is generated by\n    [sequence](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_schema_sequence/create_index/).\n    **Warning**: there is no native support for sequences in sharded systems\n    since each replicaset has its own sequence. If sequence field is a part\n    of the sharding key (which is true by default), choosing the bucket id is\n    the sole responsibility of the developer\n  * `noreturn` (`?boolean`) - suppress successfully processed tuples\n    (first return value is `nil`). `false` by default\n  * `fetch_latest_metadata` (`?boolean`) - guarantees the\n    up-to-date metadata (space format) in first return value, otherwise\n    it may not take into account the latest migration of the data format.\n    Performance overhead is up to 15%. `false` by default\n\nReturns metadata and array with inserted/replaced rows, array of errors.\nEach error object can contain field `operation_data`.\n\n`operation_data` field can contain:\n* tuple for which the error occurred;\n* object with an incorrect format;\n* tuple the operation on which was performed but\n  operation was rollback;\n* tuple the operation on which was not performed\n  because operation was stopped by error.\n\nRight now CRUD cannot provide batch replace with full consistency.\nCRUD offers batch replace with partial consistency. That means\nthat full consistency can be provided only on single replicaset\nusing `box` transactions.\n\n**Example:**\n\n```lua\ncrud.replace_many('developers', {\n  {1, box.NULL, 'Elizabeth', 'lizaaa'},\n  {2, box.NULL, 'Anastasia', 'iamnewdeveloper'},\n})\n---\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'login', 'type': 'string'}\n  rows:\n  - [1, 477, 'Elizabeth', 'lizaaa']\n  - [2, 401, 'Anastasia', 'iamnewdeveloper']\n...\ncrud.replace_object_many('developers', {\n    {id = 1, name = 'Inga', login = 'mylogin'},\n    {id = 10, name = 'Anastasia', login = 'qwerty'},\n})\n---\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows:\n  - [1, 477, 'Inga', 'mylogin']\n  - [10, 569, 'Anastasia', 'qwerty']\n\n-- Partial success\n-- Let's say login has unique secondary index\nlocal res, errs = crud.replace_object_many('developers', {\n    {id = 22, name = 'Alex', login = 'pushkinn'},\n    {id = 3, name = 'Anastasia', login = 'qwerty'},\n    {id = 5, name = 'Sergey', login = 's.petrenko'},\n})\n---\nres\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows:\n  - [5, 1172, 'Sergey', 's.petrenko'],\n  - [22, 655, 'Alex', 'pushkinn'],\n\n#errs                  -- 1\nerrs[1].class_name     -- ReplaceManyError\nerrs[1].err            -- 'Duplicate key exists \u003c...\u003e'\nerrs[1].operation_data -- {3, 2804, 'Anastasia', 'qwerty'}\n\n-- Partial success with stop and rollback on error\n-- stop_on_error = true, rollback_on_error = true\n-- two error on one storage with rollback, inserts stop by error on this storage\n-- inserts before error are rollback\nlocal res, crud.replace_object_many('developers', {\n    {id = 6, name = 'Alex', login = 'alexpushkin'},\n    {id = 92, name = 'Artur', login = 'AGolden'},\n    {id = 11, name = 'Anastasia', login = 'qwerty'},\n    {id = 4, name = 'Sergey', login = 's.smirnov'},\n    {id = 9, name = 'Anna', login = 'AnnaBlack'},\n    {id = 17, name = 'Oksana', login = 'OKonov'},\n}, {\n    stop_on_error = true,\n    rollback_on_error  = true,\n})\nres\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows:\n  - [4, 1161, 'Sergey', 's.smirnov'],\n  - [6, 1064, 'Alex', 'alexpushkin'],\n#errs                  -- 4\nerrs[1].class_name     -- ReplaceManyError\nerrs[1].err            -- 'Duplicate key exists \u003c...\u003e'\nerrs[1].operation_data -- {11, 2652, \"Anastasia\", \"qwerty\"}\n\nerrs[2].class_name     -- NotPerformedError\nerrs[2].err            -- 'Operation with tuple was not performed'\nerrs[2].operation_data -- {9, 1644, \"Anna\", \"AnnaBlack\"}\n\nerrs[3].class_name     -- NotPerformedError\nerrs[3].err            -- 'Operation with tuple was not performed'\nerrs[3].operation_data -- {17, 2900, \"Oksana\", \"OKonov\"}\n\nerrs[4].class_name     -- NotPerformedError\nerrs[4].err            -- 'Operation with tuple was rollback'\nerrs[4].operation_data -- {92, 2040, \"Artur\", \"AGolden\"}\n...\n```\n\n### Upsert\n\n```lua\n-- Upsert tuple\nlocal result, err = crud.upsert(space_name, tuple, operations, opts)\n-- Upsert object\nlocal result, err = crud.upsert_object(space_name, tuple, operations, opts)\n```\n\nwhere:\n\n* `space_name` (`string`) - name of the space\n* `tuple` / `object` (`table`) - tuple/object to insert if there is no existing tuple which matches the key fields\n* `operations` (`table`) - update [operations](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_space/update/) if there is an existing tuple which matches the key fields of tuple\n* `opts`:\n  * `timeout` (`?number`) - `vshard.call` timeout and vshard master\n    discovery timeout (in seconds), default value is 2\n  * `bucket_id` (`?number|cdata`) - bucket ID\n  * `fields` (`?table`) - field names for getting only a subset of fields\n  * `vshard_router` (`?string|table`) - Cartridge vshard group name or\n    vshard router instance. Set this parameter if your space is not\n    a part of the default vshard cluster\n  * `noreturn` (`?boolean`) - suppress successfully processed tuple\n    (first return value is `nil`). `false` by default\n  * `fetch_latest_metadata` (`?boolean`) - guarantees the\n    up-to-date metadata (space format) in first return value, otherwise\n    it may not take into account the latest migration of the data format.\n    Performance overhead is up to 15%. `false` by default\n\nReturns metadata and empty array of rows or nil, error.\n\n**Example:**\n\n```lua\ncrud.upsert('customers',\n    {1, box.NULL, 'Alice', 22},\n    {{'+', 'age', 1}})\n---\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows: []\n...\ncrud.upsert_object('customers',\n    {id = 1, name = 'Alice', age = 22},\n    {{'+', 'age', 1}})\n---\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows: []\n...\n```\n\n### Upsert many\n\n```lua\n-- Upsert batch of tuples\nlocal result, err = crud.upsert_many(space_name, tuples_operation_data, opts)\n-- Upsert batch of objects\nlocal result, err = crud.upsert_object_many(space_name, objects_operation_data, opts)\n```\n\nwhere:\n\n* `space_name` (`string`) - name of the space to insert an object\n* `tuples_operation_data` / `objects_operation_data` (`table`) - array of\n   tuples/objects to insert\n   and update [operations](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_space/#box-space-update)\n   in format {{tuple_1, operation_1}, ..., {tuple_n, operation_n}} (at least one),\n   if there is tuple with duplicate key then existing tuple will\n   be updated with update operations\n* `opts`:\n  * `timeout` (`?number`) - `vshard.call` timeout and vshard master\n    discovery timeout (in seconds), default value is 2\n  * `fields` (`?table`) - field names for getting only a subset of fields\n  * `stop_on_error` (`?boolean`) - stop on a first error and report error\n    regarding the failed operation and error about what tuples were not\n    performed, default is `false`\n  * `rollback_on_error` (`?boolean`) - any failed operation will lead to\n    rollback on a storage, where the operation is failed, report error\n    about what tuples were rollback, default is `false`\n  * `vshard_router` (`?string|table`) - Cartridge vshard group name or\n    vshard router instance. Set this parameter if your space is not\n    a part of the default vshard cluster\n  * `noreturn` (`?boolean`) - suppress successfully processed tuples\n    (first return value is `nil`). `false` by default\n  * `fetch_latest_metadata` (`?boolean`) - guarantees the\n    up-to-date metadata (space format) in first return value, otherwise\n    it may not take into account the latest migration of the data format.\n    Performance overhead is up to 15%. `false` by default\n\nReturns metadata and array of errors.\nEach error object can contain field `operation_data`.\n\n`operation_data` field can contain:\n* tuple for which the error occurred;\n* object with an incorrect format;\n* tuple the operation on which was performed but\n  operation was rollback;\n* tuple the operation on which was not performed\n  because operation was stopped by error.\n\nRight now CRUD cannot provide batch upsert with full consistency.\nCRUD offers batch upsert with partial consistency. That means\nthat full consistency can be provided only on single replicaset\nusing `box` transactions.\n\n**Example:**\n\n```lua\ncrud.upsert_many('customers', {\n    {{1, box.NULL, 'Elizabeth', 23}, {{'+', 'age', 1}}},\n    {{2, box.NULL, 'Anastasia', 22}, {{'+', 'age', 2}, {'=', 'name', 'Oleg'}}}\n})\n---\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n\n...\ncrud.upsert_object_many('customers', {\n    {{id = 3, name = 'Elizabeth', age = 24}, {{'+', 'age', 1}}},\n    {{id = 10, name = 'Anastasia', age = 21}, {{'+', 'age', 2}}}\n})\n---\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n\n-- Partial success\nlocal res, errs = crud.upsert_object_many('customers', {\n    {{id = 22, name = 'Alex', age = 34}, {{'+', 'age', 12}}},\n    {{id = 3, name = 'Anastasia', age = 22}, {{'=', 'age', 'invalid type'}}},\n    {{id = 5, name = 'Sergey', age = 25}, {{'+', 'age', 10}}}\n})\n---\nres\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n\n#errs                  -- 1\nerrs[1].class_name     -- BatchUpsertError\nerrs[1].err            -- 'Tuple field 4 (age) type does not match one required by operation \u003c...\u003e'\nerrs[1].operation_data -- {3, 2804, 'Anastasia', 22}\n...\n-- Partial success success with stop and rollback on error\n-- stop_on_error = true, rollback_on_error = true\n-- two error on one storage with rollback,\n-- inserts stop by error on this storage\n-- inserts before error are rollback\nlocal res, errs = crud.upsert_object_many('customers', {\n    {{id = 6, name = 'Alex', age = 34}, {{'+', 'age', 1}}},\n    {{id = 92, name = 'Artur', age = 29}, {{'+', 'age', 2}}},\n    {{id = 3, name = 'Anastasia', age = 22}, {{'+', 'age', '3'}}},\n    {{id = 4, name = 'Sergey', age = 25}, {{'+', 'age', 4}}},\n    {{id = 9, name = 'Anna', age = 30}, {{'+', 'age', 5}}},\n    {{id = 71, name = 'Oksana', age = 29}, {{'+', 'age', '6'}}},\n}, {\n    stop_on_error = true,\n    rollback_on_error  = true,\n})\nres\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n#errs                  -- 4\nerrs[1].class_name     -- UpsertManyError\nerrs[1].err            -- 'Duplicate key exists \u003c...\u003e'\nerrs[1].operation_data -- {3, 2804, 'Anastasia', 22}\n\nerrs[2].class_name     -- NotPerformedError\nerrs[2].err            -- 'Operation with tuple was not performed'\nerrs[2].operation_data -- {9, 1644, \"Anna\", 30}\n\nerrs[3].class_name     -- NotPerformedError\nerrs[3].err            -- 'Operation with tuple was not performed'\nerrs[3].operation_data -- {71, 1802, \"Oksana\", 29}\n\nerrs[4].class_name     -- NotPerformedError\nerrs[4].err            -- 'Operation with tuple was rollback'\nerrs[4].operation_data -- {92, 2040, \"Artur\", 29}\n```\n\n### Select\n\n`CRUD` supports multi-conditional selects, treating a cluster as a single space.\nThe conditions may include field names, as well as index names.\n(Refer to [#352](https://github.com/tarantool/crud/issues/352) for field number.)\nThe recommended first condition is a TREE index; this helps reducing the number\nof tuples to scan. Otherwise a full scan is performed.\n\n```lua\nlocal result, err = crud.select(space_name, conditions, opts)\n```\n\nwhere:\n\n* `space_name` (`string`) - name of the space\n* `conditions` (`?table`) - array of [select conditions](#select-conditions)\n* `opts`:\n  * `first` (`?number`) - the maximum count of the objects to return.\n     If negative value is specified, the objects behind `after` are returned\n     (`after` option is required in this case). [See pagination examples](doc/select.md#pagination).\n  * `after` (`?table`) - tuple after which objects should be selected\n  * `batch_size` (`?number`) - number of tuples to process per one request to storage\n  * `bucket_id` (`?number|cdata`) - bucket ID\n  * `force_map_call` (`?boolean`) - if `true`\n     then the map call is performed without any optimizations even\n     if full primary key equal condition is specified\n  * `timeout` (`?number`) - `vshard.call` timeout (in seconds)\n  * `fields` (`?table`) - field names for getting only a subset of fields\n  * `fullscan` (`?boolean`) - if `true` then a critical log entry will be skipped\n    on potentially long `select`, see [avoiding full scan](doc/select.md#avoiding-full-scan).\n  * `mode` (`?string`, `read` or `write`) - if `write` is specified then `select` is\n    performed on master, default value is `read`\n  * `prefer_replica` (`?boolean`) - if `true` then the preferred target is one of\n    the replicas\n  * `balance` (`?boolean`) - use replica according to vshard load balancing policy\n  * `vshard_router` (`?string|table`) - Cartridge vshard group name or\n    vshard router instance. Set this parameter if your space is not\n    a part of the default vshard cluster\n  * `yield_every` (`?number`) - number of tuples processed on storage to yield after,\n    `yield_every` should be \u003e 0, default value is 1000\n  * `fetch_latest_metadata` (`?boolean`) - guarantees the\n    up-to-date metadata (space format) in first return value, otherwise\n    it may not take into account the latest migration of the data format.\n    Performance overhead is up to 15%. `false` by default\n\n\nReturns metadata and array of rows, error.\n\n#### Select conditions\n\nSelect conditions are very similar to Tarantool update\n[operations](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_space/update/).\n\nEach condition is a table `{operator, field-identifier, value}`:\n\n* Supported operators are: `=` (or `==`), `\u003e`, `\u003e=`, `\u003c`, `\u003c=`.\n* Field identifier can be field name or index name. (Refer to [#352](https://github.com/tarantool/crud/issues/352) for field number.)\n\n**Example:**\n\n```lua\ncrud.select('customers', {{'\u003c=', 'age', 35}}, {first = 10})\n---\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows:\n  - [5, 1172, 'Jack', 35]\n  - [3, 2804, 'David', 33]\n  - [6, 1064, 'William', 25]\n  - [7, 693, 'Elizabeth', 18]\n  - [1, 477, 'Elizabeth', 12]\n...\n```\n\n**Note**: tuples are sorted by age because space has index `age`.\nOtherwise, tuples are sorted by primary key.\n\nSee more examples of select queries [here.](https://github.com/tarantool/crud/blob/master/doc/select.md)\n\n### Pairs\n\nYou can iterate across a distributed space using the `crud.pairs` function.\nIts arguments are the same as [`crud.select`](#select) arguments except\n`fullscan` (it does not exist because `crud.pairs` does not generate a critical\nlog entry on potentially long requests) and negative `first` values aren't\nallowed.\nUser could pass use_tomap flag (false by default) to iterate over flat tuples or objects.\n\n**Example:**\n\n```lua\nlocal tuples = {}\nfor _, tuple in crud.pairs('customers', {{'\u003c=', 'age', 35}}, {use_tomap = false}) do\n    -- {5, 1172, 'Jack', 35}\n    table.insert(tuples, tuple)\nend\n\nlocal objects = {}\nfor _, object in crud.pairs('customers', {{'\u003c=', 'age', 35}}, {use_tomap = true}) do\n    -- {id = 5, name = 'Jack', bucket_id = 1172, age = 35}\n    table.insert(objects, object)\nend\n```\n\nSee more examples of pairs queries [here.](https://github.com/tarantool/crud/blob/master/doc/pairs.md)\n\n### Min and max\n\n`CRUD` supports operations to get the minimum (maximum) object from the space index\n\n```lua\nlocal result, err = crud.min(space_name, index_id, opts)\nlocal result, err = crud.max(space_name, index_id, opts)\n```\n\nwhere:\n\n* `space_name` (`string`) - name of the space\n* `index_id` (`?string|number`) - index name or index id. Primary index by default\n* `opts`:\n  * `timeout` (`?number`) - `vshard.call` timeout (in seconds)\n  * `fields` (`?table`) - field names for getting only a subset of fields\n  * `mode` (`?string`, `read` or `write`) - if `write` is specified then `select` is\n    performed on master, default value is `read`\n  * `vshard_router` (`?string|table`) - Cartridge vshard group name or\n    vshard router instance. Set this parameter if your space is not\n    a part of the default vshard cluster\n  * `fetch_latest_metadata` (`?boolean`) - guarantees the\n    up-to-date metadata (space format) in first return value, otherwise\n    it may not take into account the latest migration of the data format.\n    Performance overhead is up to 15%. `false` by default\n\n```lua\n-- Find the minimum value in the specified index\nlocal result, err = crud.min('customers', 'age')\n---\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows:\n  - [1, 477, 'Elizabeth', 12]\n\n-- Find the maximum value in the specified index\nlocal result, err = crud.max('customers', 'age')\n---\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows:\n  - [5, 1172, 'Jack', 35]\n```\n\n### Cut extra rows\n\nYou could use `crud.cut_rows` function to cut off scan key and primary key values that were merged to the select/pairs partial result (select/pairs with `fields` option).\n\n```lua\nlocal res, err = crud.cut_rows(rows, metadata, fields)\n```\n\nwhere:\n\n* `rows` (`table`) - array of tuples for cutting\n* `metadata` (`?table`) - metadata about `rows` fields\n* `fields` (`table`) - field names of fields that should be contained in the result\n\nReturns metadata and array of rows, error.\n\nSee more examples of `crud.cut_rows` usage [here](https://github.com/tarantool/crud/blob/master/doc/select.md) and [here.](https://github.com/tarantool/crud/blob/master/doc/pairs.md)\n\n### Cut extra objects\n\nIf you use `pairs` with `use_tomap` flag and you need to cut off scan key and primary key values that were merged to the pairs partial result (pairs with `fields` option) you should use `crud.cut_objects`.\n\n```lua\nlocal new_objects = crud.cut_objects(objects, fields)\n```\n\nwhere:\n\n* `objects` (`table`) - array of objects for cutting\n* `fields` (`table`) - field names of fields that should be contained in the result\n\nReturns array of objects.\n\nSee more examples of `crud.cut_objects` usage [here.](https://github.com/tarantool/crud/blob/master/doc/pairs.md)\n\n### Truncate\n\n```lua\n-- Truncate space\nlocal result, err = crud.truncate(space_name, opts)\n```\n\nwhere:\n\n* `space_name` (`string`) - name of the space\n* `opts`:\n  * `timeout` (`?number`) - `vshard.call` timeout (in seconds)\n  * `vshard_router` (`?string|table`) - Cartridge vshard group name or\n    vshard router instance. Set this parameter if your space is not\n    a part of the default vshard cluster\n\nReturns true or nil with error.\n\n**Example:**\n\n```lua\n#crud.select('customers', {{'\u003c=', 'age', 35}}, {first = 10})\n---\n- 1\n...\ncrud.truncate('customers', {timeout = 2})\n---\n- true\n...\n#crud.select('customers', {{'\u003c=', 'age', 35}}, {first = 10})\n---\n- 0\n...\n```\n\n### Len\n\n```lua\n-- Calculates the number of tuples in the space for memtx engine\n-- Calculates the maximum approximate number of tuples in the space for vinyl engine\nlocal result, err = crud.len(space_name, opts)\n```\n\nwhere:\n\n* `space_name` (`string`) - name of the space\n* `opts`:\n  * `timeout` (`?number`) - `vshard.call` timeout and vshard master\n    discovery timeout (in seconds), default value is 2\n  * `vshard_router` (`?string|table`) - Cartridge vshard group name or\n    vshard router instance. Set this parameter if your space is not\n    a part of the default vshard cluster\n\nReturns number or nil with error.\n\nUsing space id instead of space name is also possible, but\ndeprecated and will be removed in future releases.\n\nUsing space id in crud.len and custom vshard_router is not\nsupported by statistics: space labels may be inconsistent.\n\n**Example:**\n\nUsing `memtx`:\n\n```lua\n#crud.select('customers', nil, {fullscan = true})\n---\n- 5\n...\ncrud.len('customers', {timeout = 2})\n---\n- 5\n...\n```\n\nUsing `vinyl`:\n\n```lua\ncrud.len('customers')\n---\n- 0\n...\ncrud.delete('customers', 1)\n---\n...\ncrud.len('customers')\n---\n- 1\n...\n```\n\n### Storage info\n\n```lua\n-- Get storages status\nlocal result, err = crud.storage_info(opts)\n```\n\nwhere:\n\n* `opts`:\n  * `timeout` (`?number`) -  maximum time (in seconds, default: 2) to wait for response from\n  cluster instances.\n  * `vshard_router` (`?string|table`) - Cartridge vshard group name or vshard router instance.\n\nReturns storages status table by instance UUID or nil with error. Status table fields:\n* `status` contains a string representing the status:\n  * `\"running\"` - storage is initialized and running.\n  * `\"uninitialized\"` - storage is not initialized or disabled.\n  * `\"error\"` - error getting the status from a storage. Connection error, for example.\n* `is_master` is `true` if an instance is a master, `false` - otherwise.\n* `message` is `nil` unless a problem occurs with getting storage status.\n\n\n**Example:**\n\n```lua\ncrud.storage_info()\n```\n```\n---\n- fe1b5bd9-42d4-4955-816c-3aa015e0eb81:\n    status: running\n    is_master: true\n  a1eefe51-9869-4c4c-9676-76431b08c97a:\n    status: running\n    is_master: true\n  777415f4-d656-440e-8834-7124b7267b6d:\n    status: uninitialized\n    is_master: false\n  e1b2e202-b0f7-49cd-b0a2-6b3a584f995e:\n    status: error\n    message: 'connect, called on fd 36, aka 127.0.0.1:49762: Connection refused'\n    is_master: false\n...\n```\n\n### Count\n\n`CRUD` supports multi-conditional count, treating a cluster as a single space.\nThe same as with `select()` the conditions may include field names or numbers,\nas well as index names. The recommended first condition is a TREE index; this\nhelps to reduce the number of tuples to scan. Otherwise a full scan is performed.\nIf compared with `len()`, `count()` method scans the entire space to count the\ntuples according user conditions. This method does yield that's why result may\nbe approximate. Number of tuples before next `yield()` is under control with\noption `yield_every`.\n\n```lua\nlocal result, err = crud.count(space_name, conditions, opts)\n```\n\nwhere:\n\n* `space_name` (`string`) - name of the space\n* `conditions` (`?table`) - array of [conditions](#select-conditions)\n* `opts`:\n  * `yield_every` (`?number`) - number of tuples processed to yield after,\n    `yield_every` should be \u003e 0, default value is 1000\n  * `timeout` (`?number`) - `vshard.call` timeout and vshard master\n    discovery timeout (in seconds), default value is 2\n  * `bucket_id` (`?number|cdata`) - bucket ID\n  * `force_map_call` (`?boolean`) - if `true`\n    then the map call is performed without any optimizations even,\n    default value is `false`\n  * `fullscan` (`?boolean`) - if `true` then a critical log entry will be skipped\n    on potentially long `count`, see [avoiding full scan](doc/select.md#avoiding-full-scan).\n  * `mode` (`?string`, `read` or `write`) - if `write` is specified then `count` is\n    performed on master, default value is `read`\n  * `prefer_replica` (`?boolean`) - if `true` then the preferred target is one of\n    the replicas, default value is `false`\n  * `balance` (`?boolean`) - use replica according to\n    [vshard load balancing policy](https://www.tarantool.io/en/doc/latest/reference/reference_rock/vshard/vshard_api/#router-api-call),\n    default value is `false`\n  * `vshard_router` (`?string|table`) - Cartridge vshard group name or\n    vshard router instance. Set this parameter if your space is not\n    a part of the default vshard cluster\n\n```lua\ncrud.count('customers', {{'==', 'age', 35}})\n---\n- 1\n...\n```\n\n### Call options for crud methods\n\nCombinations of `mode`, `prefer_replica` and `balance` options lead to:\n\n* `mode` == `write` - method performed on master with vshard call `callrw`\n* `mode` == `read`\n  * not prefer_replica, not balance -\n    [vshard call `callro`](https://www.tarantool.io/en/doc/latest/reference/reference_rock/vshard/vshard_api/#router-api-callro)\n  * not prefer_replica, balance -\n    [vshard call `callbro`](https://www.tarantool.io/en/doc/latest/reference/reference_rock/vshard/vshard_api/#router-api-callbro)\n  * prefer_replica, not balance -\n    [vshard call `callre`](https://www.tarantool.io/en/doc/latest/reference/reference_rock/vshard/vshard_api/#router-api-callre)\n  * prefer_replica, balance -\n    [vshard call `callbre`](https://www.tarantool.io/en/doc/latest/reference/reference_rock/vshard/vshard_api/#router-api-callbre)\n\n### Statistics\n\n`crud` routers can provide statistics on called operations.\n```lua\n-- Enable statistics collect.\ncrud.cfg{ stats = true }\n\n-- Returns table with statistics information.\ncrud.stats()\n\n-- Returns table with statistics information for specific space.\ncrud.stats('my_space')\n\n-- Disables statistics collect and destroys all collectors.\ncrud.cfg{ stats = false }\n\n-- Destroys all statistics collectors and creates them again.\ncrud.reset_stats()\n```\n\nIf [`metrics`](https://github.com/tarantool/metrics) `0.10.0` or greater\nfound, metrics collectors will be used by default to store statistics\ninstead of local collectors. Quantiles in metrics summary collections\nare disabled by default. You can manually choose driver and enable quantiles.\n```lua\n-- Use simple local collectors (default if no required metrics version found).\ncrud.cfg{ stats = true, stats_driver = 'local' }\n\n-- Use metrics collectors (default if metrics rock found).\ncrud.cfg{ stats = true, stats_driver = 'metrics' }\n\n-- Use metrics collectors with 0.99 quantiles.\ncrud.cfg{ stats = true, stats_driver = 'metrics', stats_quantiles = true }\n```\n\nYou can use `crud.cfg` to check current stats state.\n```lua\ncrud.cfg\n---\n- stats_quantiles: true\n  stats: true\n  stats_driver: metrics\n...\n```\nPerformance overhead is 3-10% in case of `local` driver and\n5-15% in case of `metrics` driver, up to 20% for `metrics` with quantiles.\n\nBeware that iterating through `crud.cfg` with pairs is not supported yet,\nrefer to [tarantool/crud#265](https://github.com/tarantool/crud/issues/265).\n\nFormat is as follows.\n```lua\ncrud.stats()\n---\n- spaces:\n    my_space:\n      insert:\n        ok:\n          latency: 0.0015\n          latency_average: 0.002\n          latency_quantile_recent: 0.0015\n          count: 19800\n          time: 39.6\n        error:\n          latency: 0.0000008\n          latency_average: 0.000001\n          latency_quantile_recent: 0.0000008\n          count: 4\n          time: 0.000004\n...\ncrud.stats('my_space')\n---\n- insert:\n    ok:\n      latency: 0.0015\n      latency_average: 0.002\n      latency_quantile_recent: 0.0015\n      count: 19800\n      time: 39.6\n    error:\n      latency: 0.0000008\n      latency_average: 0.000001\n      latency_quantile_recent: 0.0000008\n      count: 4\n      time: 0.000004\n...\n```\n`spaces` section contains statistics for each observed space.\nIf operation has never been called for a space, the corresponding\nfield will be empty. If no requests has been called for a\nspace, it will not be represented. Space data is based on\nclient requests rather than storages schema, so requests\nfor non-existing spaces are also collected.\n\nPossible statistics operation labels are\n`insert` (for `insert` and `insert_object` calls),\n`get`, `replace` (for `replace` and `replace_object` calls), `update`,\n`upsert` (for `upsert` and `upsert_object` calls), `delete`,\n`select` (for `select` and `pairs` calls), `truncate`, `len`, `count`\nand `borders` (for `min` and `max` calls).\n\nEach operation section consists of different collectors\nfor success calls and error (both error throw and `nil, err`)\nreturns. `count` is the total requests count since instance start\nor stats restart.  `time` is the total time of requests execution.\n`latency_average` is `time` / `count`.\n`latency_quantile_recent` is the 0.99 quantile of request execution\ntime for a recent period (see\n[`metrics` summary API](https://www.tarantool.io/ru/doc/latest/book/monitoring/api_reference/#summary)).\nIt is computed only if `metrics` driver is used and quantiles are\nenabled. `latency_quantile_recent` value may be `-nan` if there\nwasn't any observations for several ages, see\n[tarantool/metrics#303](https://github.com/tarantool/metrics/issues/303).\n`latency` is a `latency_quantile_recent` if `metrics` driver is used\nand quantiles are enabled, otherwise it's `latency_average`.\n\nIn [`metrics`](https://www.tarantool.io/en/doc/latest/book/monitoring/)\nregistry statistics are stored as `tnt_crud_stats` metrics\nwith `operation`, `status` and `name` labels.\n```\nmetrics:collect()\n---\n- - label_pairs:\n      status: ok\n      operation: insert\n      name: customers\n    value: 221411\n    metric_name: tnt_crud_stats_count\n  - label_pairs:\n      status: ok\n      operation: insert\n      name: customers\n    value: 10.49834896344692\n    metric_name: tnt_crud_stats_sum\n  - label_pairs:\n      status: ok\n      operation: insert\n      name: customers\n      quantile: 0.99\n    value: 0.00023606420935973\n    metric_name: tnt_crud_stats\n...\n```\nIf you see `-Inf` value in quantile metrics, try to decrease the tolerated error:\n```lua\ncrud.cfg{stats_quantile_tolerated_error = 1e-4}\n```\nSee [tarantool/metrics#189](https://github.com/tarantool/metrics/issues/189) for\ndetails about the issue.\nYou can also configure quantile `age_bucket_count` (default: 2) and\n`max_age_time` (in seconds, default: 60):\n```lua\ncrud.cfg{\n    stats_quantile_age_bucket_count = 3,\n    stats_quantile_max_age_time = 30,\n}\n```\nSee [`metrics` summary API](https://www.tarantool.io/ru/doc/latest/book/monitoring/api_reference/#summary)\nfor details. These parameters can be used to smooth time window move\nor reduce the amount on `-nan` gaps for low request frequency applications.\n\n`select` section additionally contains `details` collectors.\n```lua\ncrud.stats('my_space').select.details\n---\n- map_reduces: 4\n  tuples_fetched: 10500\n  tuples_lookup: 238000\n...\n```\n`map_reduces` is the count of planned map reduces (including those not\nexecuted successfully). `tuples_fetched` is the count of tuples fetched\nfrom storages during execution, `tuples_lookup` is the count of tuples\nlooked up on storages while collecting responses for calls (including\nscrolls for multibatch requests). Details data is updated as part of\nthe request process, so you may get new details before `select`/`pairs`\ncall is finished and observed with count, latency and time collectors.\nIn [`metrics`](https://www.tarantool.io/en/doc/latest/book/monitoring/)\nregistry they are stored as `tnt_crud_map_reduces`,\n`tnt_crud_tuples_fetched` and `tnt_crud_tuples_lookup` metrics\nwith `{ operation = 'select', name = space_name }` labels.\n\nSince `pairs` request behavior differs from any other crud request, its\nstatistics collection also has specific behavior. Statistics (`select`\nsection) are updated after `pairs` cycle is finished: you\neither have iterated through all records or an error was thrown.\nIf your pairs cycle was interrupted with `break`, statistics will\nbe collected when pairs objects are cleaned up with Lua garbage\ncollector.\n\nStatistics are preserved between package reloads. Statistics are preserved\nbetween [Tarantool Cartridge role reloads](https://www.tarantool.io/en/doc/latest/book/cartridge/cartridge_api/modules/cartridge.roles/#reload)\nif you use CRUD Cartridge roles. Beware that metrics 0.12.0 and below do not\nsupport preserving stats between role reload\n(see [tarantool/metrics#334](https://github.com/tarantool/metrics/issues/334)),\nthus this feature will be unsupported for `metrics` driver.\n\n### Read view\n\nA read view is an in-memory snapshot of data on instance that isn’t affected by future data modifications. Read views allow you to retrieve data using the `read_view_object:select()` and `read_view_object:pairs()` operations.\n\nRead views can be used to make complex analytical queries. This reduces the load on the main database and improves RPS for a single Tarantool instance.\n\nRead views have the following limitations:\n\n  * Only the memtx engine is supported.\n  * Read view can be used starting from Tarantool Enterprise v2.11.0.\n  * There is no clusterwide readview support. For a sharded cluster, we open a readview on each storage. Due to a cluster's distributed nature, it is not guaranteed that they will open simultaneously.\n\n#### Creating a read view\n\nTo create a read view, call the `crud.readview()` function.\n\n```lua\nlocal rv = crud.readview(opts)\n```\n\nwhere:\n\n* `opts`:\n  * `name` (`?string`) - name of the read view\n  * `timeout` (`?number`) - `vshard.call` timeout (in seconds)\n\n**Example:**\n\n```lua\nlocal rv = crud.readview({name = 'foo', timeout = 3})\n```\n\n#### Closing a read view\n\nWhen a read view is no longer needed, close it using the `read_view_object:close()` method because a read view may consume a substantial amount of memory.\n\n```lua\nlocal rv = crud.readview()\nrv:close(opts)\n```\n\nwhere:\n\n* `opts`:\n  * `timeout` (`?number`) - `vshard.call` timeout (in seconds)\n\nA read view is also closed implicitly when the read view object is collected by the Lua garbage collector.\n\n**Example:**\n\n```lua\nlocal rv = crud.readview()\nrv:close({timeout = 3})\n```\n\n#### Read view select\n\n`read_view_object:select()` supports multi-conditional selects, treating a cluster as a single space, same as `crud.select`.\n\n```lua\nlocal rv = crud.readview()\nlocal result, err = rv:select(space_name, conditions, opts)\nrv:close()\n```\n\nOpts are the same as [select opts](#select), except `balance`, `prefer_replica` and `mode` are not supported.\n\nReturns metadata and array of rows, error.\n\n**Example:**\n\n```lua\nlocal rv = crud.readview()\nrv:select('customers', nil, {batch_size=1, fullscan=true})\n---\n- metadata: [{'name': 'id', 'type': 'unsigned'}, {'name': 'bucket_id', 'type': 'unsigned'},\n    {'name': 'name', 'type': 'string'}, {'name': 'age', 'type': 'number'}]\n  rows:\n  - [1, 477, 'Elizabeth', 12]\n  - [2, 401, 'Mary', 46]\n  - [3, 2804, 'David', 33]\n  - [4, 1161, 'William', 81]\n  - [5, 1172, 'Jack', 35]\n  - [6, 1064, 'William', 25]\n  - [7, 693, 'Elizabeth', 18]\n- null\n...\ncrud.insert('customers', {8, box.NULL, 'Elizabeth', 23})\n---\n- rows:\n  - [8, 185, 'Elizabeth', 23]\n  metadata: [{'name': 'id', 'type': 'unsigned'}, {'name': 'bucket_id', 'type': 'unsigned'},\n    {'name': 'name', 'type': 'string'}, {'name': 'age', 'type': 'number'}]\n- null\n...\nrv:select('customers', nil, {batch_size=1, fullscan=true})\n---\n- metadata: [{'name': 'id', 'type': 'unsigned'}, {'name': 'bucket_id', 'type': 'unsigned'},\n    {'name': 'name', 'type': 'string'}, {'name': 'age', 'type': 'number'}]\n  rows:\n  - [1, 477, 'Elizabeth', 12]\n  - [2, 401, 'Mary', 46]\n  - [3, 2804, 'David', 33]\n  - [4, 1161, 'William', 81]\n  - [5, 1172, 'Jack', 35]\n  - [6, 1064, 'William', 25]\n  - [7, 693, 'Elizabeth', 18]\n- null\n...\nrv:close()\n```\n\n##### Read view select conditions\n\nSelect conditions for `read_view_object:select()` are the same as [select conditions](#select-conditions) for `crud.select`.\n\n**Example:**\n\n```lua\nrv = crud.readview()\nrv:select('customers', {{'\u003c=', 'age', 35}}, {first = 10})\n---\n- metadata:\n  - {'name': 'id', 'type': 'unsigned'}\n  - {'name': 'bucket_id', 'type': 'unsigned'}\n  - {'name': 'name', 'type': 'string'}\n  - {'name': 'age', 'type': 'number'}\n  rows:\n  - [5, 1172, 'Jack', 35]\n  - [3, 2804, 'David', 33]\n  - [6, 1064, 'William', 25]\n  - [7, 693, 'Elizabeth', 18]\n  - [1, 477, 'Elizabeth', 12]\n...\nrv.close()\n```\n\n#### Read view pairs\n\nYou can iterate across a distributed space using the `read_view_object:pairs()` method.\nIts arguments are the same as [`crud.readview.select`](#read-view-select) arguments except\n`fullscan` (it does not exist because `crud.pairs` does not generate a critical\nlog entry on potentially long requests) and negative `first` values aren't\nallowed.\nUser could pass `use_tomap` flag (`false` by default) to iterate over flat tuples or objects.\n\n**Example:**\n\n```lua\nrv = crud.readview()\nlocal tuples = {}\nfor _, tuple in rv:pairs('customers', {{'\u003c=', 'age', 35}}, {use_tomap = false}) do\n    -- {5, 1172, 'Jack', 35}\n    table.insert(tuples, tuple)\nend\n\nlocal objects = {}\nfor _, object in rv:pairs('customers', {{'\u003c=', 'age', 35}}, {use_tomap = true}) do\n    -- {id = 5, name = 'Jack', bucket_id = 1172, age = 35}\n    table.insert(objects, object)\nend\nrv:close()\n```\n\n### Schema\n\n`crud` routers provide API to introspect spaces schema.\n\n```lua\nlocal schema, err = crud.update(space_name, opts)\n```\n\nwhere:\n\n* `space_name` (`?string`) - name of the space (if `nil`, provides info for all spaces)\n* `opts`:\n  * `timeout` (`?number`) - `vshard.call` timeout and vshard master\n    discovery timeout (in seconds), default value is 2\n  * `vshard_router` (`?string|table`) - Cartridge vshard group name or\n    vshard router instance. Set this parameter if your space is not\n    a part of the default vshard cluster\n  * `cached` (`?boolean`) - if `false`, reloads storages schema on call;\n    if `true`, return last known schema; default value is `false`.\n    Beware that consequent calls with `cached=true` do not guarantee\n    the same result if schema had chaned since net.box connections\n    still may perform reload on internal ping or any other request\n\nReturns space schema (or spaces schema map), error.\n\nBeware that schema info is not exactly the same as underlying storage spaces schema.\nThe reason is that `crud` generates `bucket_id`, if it isn't provided,\nso this field is actually nullable for a `crud` user. We also do not expose\n`bucket_id` index info since it's a vshard utility and do not related\nto application logic.\n\n**Example:**\n\n```lua\ncrud.schema('customers')\n---\n- format:\n    - name: id\n      type: unsigned\n    - name: bucket_id\n      type: unsigned\n      is_nullable: true\n    - name: name\n      type: string\n    - name: age\n      type: number\n  indexes:\n    0:\n      unique: true\n      parts:\n      - fieldno: 1\n        type: unsigned\n        exclude_null: false\n        is_nullable: false\n      id: 0\n      type: TREE\n      name: primary_index\n    2:\n      unique: false\n      parts:\n      - fieldno: 4\n        type: number\n        exclude_null: false\n        is_nullable: false\n      id: 2\n      type: TREE\n      name: age\n...\n```\n\n```lua\ncrud.schema()\n---\n- customers:\n    format: ...\n    indexes: ...\n  shops:\n    format: ...\n    indexes: ...\n```\n\n## Tarantool 3 roles\n\n`roles.crud-storage` is a Tarantool 3 role that initializes functions that\nare used on the storage side to perform CRUD operations. Role must be enabled\non sharding storages.\n\n`cartridge.roles.crud-router` is a role that exposes public `crud` functions\nto the global scope so that you can call them via `net.box` or with connectors.\nRole must be enabled on sharding routers.\n\nRoles support Tarantool 3.0.2, Tarantool 3.1.0 and newer.\nOlder versions are not supported due to\n[tarantool/tarantool#9643](https://github.com/tarantool/tarantool/issues/9643) and\n[tarantool/tarantool#9649](https://github.com/tarantool/tarantool/issues/9649)\nissues.\n\n### Usage\n\n1.  Add `crud` to dependencies in the project rockspec.\n\n    **Note**: it's better to use tagged version than `scm-1`.\n    Check the latest available [release](https://github.com/tarantool/crud/releases) tag and use it.\n\n    ```lua\n    -- \u003cproject-name\u003e-scm-1.rockspec\n    dependencies = {\n       ...\n       'crud == \u003cthe-latest-tag\u003e-1',\n        ...\n    }\n    ```\n\n2.  Add crud roles to your application configuration.\n    Application must be a sharded one.\n    It is required that `roles.crud-storage` is enabled on each\n    sharding storage.\n\n    ```yaml\n    groups:\n      routers:\n        sharding:\n          roles:\n            - router\n        roles:\n          - roles.crud-router\n        replicasets:\n          router:\n\n      storages:\n        sharding:\n          roles:\n            - storage\n        roles:\n          - roles.crud-storage\n        replicasets:\n          s-1:\n          s-2:\n    ```\n\n    \u003cdetails\u003e\n      \u003csummary\u003eFull configuration example\u003c/summary\u003e\n      \n      ```yaml\n      credentials:\n        users:\n          replicator:\n            password: replicating\n            roles:\n              - replication\n          storage:\n            password: storing-buckets\n            roles:\n              - sharding\n          guest:\n            roles:\n              - super\n\n      sharding:\n        bucket_count: 30000\n\n      replication:\n        failover: manual\n\n      iproto:\n        advertise:\n          peer:\n            login: replicator\n          sharding:\n            login: storage\n\n      groups:\n        routers:\n          sharding:\n            roles:\n            - router\n          roles:\n            - roles.crud-router\n          app:\n            module: myrouter\n          replicasets:\n            router:\n              leader: router\n              instances:\n                router:\n                  iproto:\n                    listen:\n                      - uri: localhost:3301\n        storages:\n          sharding:\n            roles:\n            - storage\n          roles:\n            - roles.crud-storage\n          app:\n            module: mystorage\n          replicasets:\n            s-1:\n              leader: s1-master\n              instances:\n                s1-master:\n                  iproto:\n                    listen:\n                      - uri: localhost:3302\n                s1-replica:\n                  iproto:\n                    listen:\n                      - uri: localhost:3303\n            s-2:\n              leader: s2-master\n              instances:\n                s2-replica:\n                  iproto:\n                    listen:\n                      - uri: localhost:3304\n                s2-master:\n                  iproto:\n                    listen:\n                      - uri: localhost:3305\n      ```\n    \u003c/details\u003e\n\n3. Bootstrap vshard routers (for example, through `app.module` section\n   in Tarantool 3 routers configuration).\n\n    ```lua\n    -- myrouter.lua\n\n    local clock = require('clock')\n    local fiber = require('fiber')\n    local log = require('log')\n\n    local vshard = require('vshard')\n\n    local TIMEOUT = 60\n    local DELAY = 0.1\n\n    local start = clock.monotonic()\n    while clock.monotonic() - start \u003c TIMEOUT do\n        local ok, err = vshard.router.bootstrap({\n            if_not_bootstrapped = true,\n        })\n\n        if ok then\n            break\n        end\n\n        log.info(('Router bootstrap error: %s'):format(err))\n        fiber.sleep(DELAY)\n    end\n    ```\n\n4. Set up your schema on storages (for example, through `app.module` section\n   in Tarantool 3 storages configuration).\n\n    ```lua\n    -- mystorage.lua\n\n    -- Schema setup is idempotent.\n    box.watch('box.status', function()\n        if box.info.ro then\n            return\n        end\n\n        local customers_space = box.schema.space.create('customers', {\n            format = {\n                {name = 'id', type = 'unsigned'},\n                {name = 'bucket_id', type = 'unsigned'},\n                {name = 'name', type = 'string'},\n                {name = 'age', type = 'number'},\n            },\n            if_not_exists = true,\n        })\n\n        customers_space:create_index('id', {\n            parts = { {field ='id', is_nullable = false} },\n            if_not_exists = true,\n        })\n\n        customers_space:create_index('bucket_id', {\n            parts = { {field ='bucket_id', is_nullable = false} },\n            if_not_exists = true,\n        })\n\n        customers_space:create_index('age', {\n            parts = { {field ='age'} },\n            unique = false,\n            if_not_exists = true,\n        })\n    end)\n    ```\n\n5.  Start the application cluster. You can check whether asynchronous bootstrap\n    had finished through `crud.storage_info()` calls on router.\n\n6.  Configure the statistics with roles configuration\n    (see `crud.cfg` options in [statistics](#statistics) section):\n    ```yaml\n    roles:\n      - roles.crud-router\n    roles_cfg:\n      roles.crud-router:\n        stats: true\n        stats_driver: metrics\n        stats_quantiles: true\n        stats_quantile_tolerated_error: 0.001\n        stats_quantile_age_buckets_count: 5\n        stats_quantile_max_age_time: 180\n    ```\n\nNow your cluster contains storages that are configured to be used for\nCRUD-operations.\nYou can simply call CRUD functions on the router to insert, select, and update\ndata across the cluster.\n\n## Cartridge roles\n\n`cartridge.roles.crud-storage` is a Tarantool Cartridge role that depends on the\n`vshard-storage` role, but also initializes functions that\nare used on the storage side to perform CRUD operations.\n\n`cartridge.roles.crud-router` is a role that depends on the\n`vshard-router` role, but also exposes public `crud` functions in the global\nscope, so that you can call them via `net.box`.\n\n\n### Usage\n\n1. Add `crud` to dependencies in the project rockspec.\n\n    **Note**: it's better to use tagged version than `scm-1`.\n    Check the latest available [release](https://github.com/tarantool/crud/releases) tag and use it.\n\n    ```lua\n    -- \u003cproject-name\u003e-scm-1.rockspec\n    dependencies = {\n        ...\n        'crud == \u003cthe-latest-tag\u003e-1',\n        ...\n    }\n    ```\n\n2. Create the role that stores your data and depends on `crud-storage`.\n\n    ```lua\n    -- app.roles.customers-storage.lua\n    local cartridge = require('cartridge')\n\n    return {\n            role_name = 'customers-storage',\n            init = function()\n                local customers_space = box.schema.space.create('customers', {\n                    format = {\n                        {name = 'id', type = 'unsigned'},\n                        {name = 'bucket_id', type = 'unsigned'},\n                        {name = 'name', type = 'string'},\n                        {name = 'age', type = 'number'},\n                    },\n                    if_not_exists = true,\n                })\n                customers_space:create_index('id', {\n                    parts = { {field ='id', is_nullable = false} },\n                    if_not_exists = true,\n                })\n                customers_space:create_index('bucket_id', {\n                    parts = { {field ='bucket_id', is_nullable = false} },\n                    if_not_exists = true,\n                })\n                customers_space:create_index('age', {\n                    parts = { {field ='age'} },\n                    unique = false,\n                    if_not_exists = true,\n                })\n            end,\n            dependencies = {'cartridge.roles.crud-storage'},\n        }\n    ```\n\n    ```lua\n    -- app.roles.customers-router.lua\n    local cartridge = require('cartridge')\n    return {\n            role_name = 'customers-router',\n            dependencies = {'cartridge.roles.crud-router'},\n        }\n    ```\n\n3.  Start the application and create `customers-storage` and\n    `customers-router` replica sets.\n\n4.  Don't forget to bootstrap vshard.\n\n5.  Configure the statistics with clusterwide configuration\n    (see `crud.cfg` options in [statistics](#statistics) section):\n    ```yaml\n    crud:\n      stats: true\n      stats_driver: metrics\n      stats_quantiles: true\n      stats_quantile_tolerated_error: 0.001\n      stats_quantile_age_buckets_count: 5\n      stats_quantile_max_age_time: 180\n    ```\n\nNow your cluster contains storages that are configured to be used for\nCRUD-operations.\nYou can simply call CRUD functions on the router to insert, select, and update\ndata across the cluster.\n\n## License\n\nBSD-2-Clause. See the [LICENSE](LICENSE) file.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftarantool%2Fcrud","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftarantool%2Fcrud","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftarantool%2Fcrud/lists"}