Tarantool CE/EE Documentation portal logo
Помощь
Обновлена 15 сентября 2026 г. в 08:55

space_object:create_index()

create_index(index-name [, index_opts ])

Параметры:

Возвращает

объект индекса

Тип возвращаемого значения

index_object

  • слишком много частей
  • индекс '...' уже существует
  • первичный ключ должен быть уникальным

Сборка или пересборка большого индекса будет периодически вызывать передачу управления, чтобы не блокировать другие запросы. А если другой запрос приведет к ошибке, например повторяющийся ключ в уникальном индексе, сборка или пересборка такого индекса не выполнится.

Пример:

local fio = require('fio')local server = require('luatest.server')local t = require('luatest')local g = t.group()g.before_each(function(cg)    cg.server = server:new {        box_cfg = {},        workdir = fio.cwd() .. '/tmp'    }    cg.server:start()end)g.after_each(function(cg)    cg.server:drop()    fio.rmtree(cg.server.workdir)end)g.test_indexes = function(cg)    cg.server:exec(function()        -- Create a space --        bands = box.schema.space.create('bands')        -- Specify field names and types --        box.space.bands:format({            { name = 'id', type = 'unsigned' },            { name = 'band_name', type = 'string' },            { name = 'year', type = 'unsigned' }        })        -- Create a primary index --        box.space.bands:create_index('primary', { parts = { 'id' } })        -- Create a unique secondary index --        box.space.bands:create_index('band', { parts = { 'band_name' } })        -- Create a non-unique secondary index --        box.space.bands:create_index('year', { parts = { { 'year' } }, unique = false })        -- Create a multi-part index --        box.space.bands:create_index('year_band', { parts = { { 'year' }, { 'band_name' } } })        -- Insert test data --        box.space.bands:insert { 1, 'Roxette', 1986 }        box.space.bands:insert { 2, 'Scorpions', 1965 }        box.space.bands:insert { 3, 'Ace of Base', 1987 }        box.space.bands:insert { 4, 'The Beatles', 1960 }        box.space.bands:insert { 5, 'Pink Floyd', 1965 }        box.space.bands:insert { 6, 'The Rolling Stones', 1962 }        box.space.bands:insert { 7, 'The Doors', 1965 }        box.space.bands:insert { 8, 'Nirvana', 1987 }        box.space.bands:insert { 9, 'Led Zeppelin', 1968 }        box.space.bands:insert { 10, 'Queen', 1970 }        -- Select a tuple by the specified primary key value --        select_primary = bands.index.primary:select { 1 }        --[[        ---        - - [1, 'Roxette', 1986]        ...        --]]        -- Select a tuple by the specified secondary key value --        select_secondary = bands.index.band:select { 'The Doors' }        --[[        ---        - - [7, 'The Doors', 1965]        ...        --]]        -- Select a tuple by the specified multi-part secondary key value --        select_multipart = bands.index.year_band:select { 1960, 'The Beatles' }        --[[        ---        - - [4, 'The Beatles', 1960]        ...        --]]        -- Select tuples by the specified partial key value --        select_multipart_partial = bands.index.year_band:select { 1965 }        --[[        ---        - - [5, 'Pink Floyd', 1965]          - [2, 'Scorpions', 1965]          - [7, 'The Doors', 1965]        ...        --]]        -- Select maximum 3 tuples by the specified secondary index --        select_limit = bands.index.band:select({}, { limit = 3 })        --[[        ---        - - [3, 'Ace of Base', 1987]          - [9, 'Led Zeppelin', 1968]          - [8, 'Nirvana', 1987]        ...        --]]        -- Select maximum 3 tuples with the key value greater than 1965 --        select_greater = bands.index.year:select({ 1965 }, { iterator = 'GT', limit = 3 })        --[[        ---        - - [9, 'Led Zeppelin', 1968]          - [10, 'Queen', 1970]          - [1, 'Roxette', 1986]        ...        --]]        -- Select maximum 3 tuples after the specified tuple --        select_after_tuple = bands.index.primary:select({}, { after = { 4, 'The Beatles', 1960 }, limit = 3 })        --[[        ---        - - [5, 'Pink Floyd', 1965]          - [6, 'The Rolling Stones', 1962]          - [7, 'The Doors', 1965]        ...        --]]        -- Select first 3 tuples and fetch a last tuple's position --        result, position = bands.index.primary:select({}, { limit = 3, fetch_pos = true })        -- Then, pass this position as the 'after' parameter --        select_after_position = bands.index.primary:select({}, { limit = 3, after = position })        --[[        ---        - - [4, 'The Beatles', 1960]          - [5, 'Pink Floyd', 1965]          - [6, 'The Rolling Stones', 1962]        ...        --]]        -- Tests --        t.assert_equals(select_primary[1], { 1, 'Roxette', 1986 })        t.assert_equals(select_secondary[1], { 7, 'The Doors', 1965 })        t.assert_equals(select_multipart[1], { 4, 'The Beatles', 1960 })        t.assert_equals(select_multipart_partial, { { 5, 'Pink Floyd', 1965 }, { 2, 'Scorpions', 1965 }, { 7, 'The Doors', 1965 } })        t.assert_equals(select_limit[1], { 3, 'Ace of Base', 1987 })        t.assert_equals(select_greater, { { 9, 'Led Zeppelin', 1968 }, { 10, 'Queen', 1970 }, { 1, 'Roxette', 1986 } })        t.assert_equals(select_after_tuple, { { 5, 'Pink Floyd', 1965 }, { 6, 'The Rolling Stones', 1962 }, { 7, 'The Doors', 1965 } })        t.assert_equals(select_after_position, { { 4, 'The Beatles', 1960 }, { 5, 'Pink Floyd', 1965 }, { 6, 'The Rolling Stones', 1962 } })    end)end

index_opts

Параметры индекса, включающие имя индекса, тип, идентификаторы ключевых полей и так далее. Эти параметры передаются в метод space_object.create_index().

type

Тип индекса.

Тип: string

По умолчанию: TREE

Возможные значения: TREE`, `HASH`, `RTREE`, `BITSET

id

Уникальный числовой идентификатор индекса, который генерируется автоматически.

Тип: number

По умолчанию: ID последнего индекса + 1

unique

Указывает, может ли индекс быть уникальным. Если значение true, индекс не может содержать одно и то же значение ключа дважды.

Тип: boolean

По умолчанию: true

Пример:

local fio = require('fio')local server = require('luatest.server')local t = require('luatest')local g = t.group()g.before_each(function(cg)    cg.server = server:new {        box_cfg = {},        workdir = fio.cwd() .. '/tmp'    }    cg.server:start()end)g.after_each(function(cg)    cg.server:drop()    fio.rmtree(cg.server.workdir)end)g.test_indexes = function(cg)    cg.server:exec(function()        -- Create a space --        bands = box.schema.space.create('bands')        -- Specify field names and types --        box.space.bands:format({            { name = 'id', type = 'unsigned' },            { name = 'band_name', type = 'string' },            { name = 'year', type = 'unsigned' }        })        -- Create a primary index --        box.space.bands:create_index('primary', { parts = { 'id' } })        -- Create a unique secondary index --        box.space.bands:create_index('band', { parts = { 'band_name' } })        -- Create a non-unique secondary index --        box.space.bands:create_index('year', { parts = { { 'year' } }, unique = false })        -- Create a multi-part index --        box.space.bands:create_index('year_band', { parts = { { 'year' }, { 'band_name' } } })        -- Insert test data --        box.space.bands:insert { 1, 'Roxette', 1986 }        box.space.bands:insert { 2, 'Scorpions', 1965 }        box.space.bands:insert { 3, 'Ace of Base', 1987 }        box.space.bands:insert { 4, 'The Beatles', 1960 }        box.space.bands:insert { 5, 'Pink Floyd', 1965 }        box.space.bands:insert { 6, 'The Rolling Stones', 1962 }        box.space.bands:insert { 7, 'The Doors', 1965 }        box.space.bands:insert { 8, 'Nirvana', 1987 }        box.space.bands:insert { 9, 'Led Zeppelin', 1968 }        box.space.bands:insert { 10, 'Queen', 1970 }        -- Select a tuple by the specified primary key value --        select_primary = bands.index.primary:select { 1 }        --[[        ---        - - [1, 'Roxette', 1986]        ...        --]]        -- Select a tuple by the specified secondary key value --        select_secondary = bands.index.band:select { 'The Doors' }        --[[        ---        - - [7, 'The Doors', 1965]        ...        --]]        -- Select a tuple by the specified multi-part secondary key value --        select_multipart = bands.index.year_band:select { 1960, 'The Beatles' }        --[[        ---        - - [4, 'The Beatles', 1960]        ...        --]]        -- Select tuples by the specified partial key value --        select_multipart_partial = bands.index.year_band:select { 1965 }        --[[        ---        - - [5, 'Pink Floyd', 1965]          - [2, 'Scorpions', 1965]          - [7, 'The Doors', 1965]        ...        --]]        -- Select maximum 3 tuples by the specified secondary index --        select_limit = bands.index.band:select({}, { limit = 3 })        --[[        ---        - - [3, 'Ace of Base', 1987]          - [9, 'Led Zeppelin', 1968]          - [8, 'Nirvana', 1987]        ...        --]]        -- Select maximum 3 tuples with the key value greater than 1965 --        select_greater = bands.index.year:select({ 1965 }, { iterator = 'GT', limit = 3 })        --[[        ---        - - [9, 'Led Zeppelin', 1968]          - [10, 'Queen', 1970]          - [1, 'Roxette', 1986]        ...        --]]        -- Select maximum 3 tuples after the specified tuple --        select_after_tuple = bands.index.primary:select({}, { after = { 4, 'The Beatles', 1960 }, limit = 3 })        --[[        ---        - - [5, 'Pink Floyd', 1965]          - [6, 'The Rolling Stones', 1962]          - [7, 'The Doors', 1965]        ...        --]]        -- Select first 3 tuples and fetch a last tuple's position --        result, position = bands.index.primary:select({}, { limit = 3, fetch_pos = true })        -- Then, pass this position as the 'after' parameter --        select_after_position = bands.index.primary:select({}, { limit = 3, after = position })        --[[        ---        - - [4, 'The Beatles', 1960]          - [5, 'Pink Floyd', 1965]          - [6, 'The Rolling Stones', 1962]        ...        --]]        -- Tests --        t.assert_equals(select_primary[1], { 1, 'Roxette', 1986 })        t.assert_equals(select_secondary[1], { 7, 'The Doors', 1965 })        t.assert_equals(select_multipart[1], { 4, 'The Beatles', 1960 })        t.assert_equals(select_multipart_partial, { { 5, 'Pink Floyd', 1965 }, { 2, 'Scorpions', 1965 }, { 7, 'The Doors', 1965 } })        t.assert_equals(select_limit[1], { 3, 'Ace of Base', 1987 })        t.assert_equals(select_greater, { { 9, 'Led Zeppelin', 1968 }, { 10, 'Queen', 1970 }, { 1, 'Roxette', 1986 } })        t.assert_equals(select_after_tuple, { { 5, 'Pink Floyd', 1965 }, { 6, 'The Rolling Stones', 1962 }, { 7, 'The Doors', 1965 } })        t.assert_equals(select_after_position, { { 4, 'The Beatles', 1960 }, { 5, 'Pink Floyd', 1965 }, { 6, 'The Rolling Stones', 1962 } })    end)end

if_not_exists

Указывает, следует ли игнорировать ошибку при попытке создать индекс с дублирующимся именем.

Тип: boolean

По умолчанию: false

parts

Указывает ключевые части индекса.

Тип: таблица значений [key_part](#key_part_object)

По умолчанию: {1, ‘unsigned’}

Пример:

local fio = require('fio')local server = require('luatest.server')local t = require('luatest')local g = t.group()g.before_each(function(cg)    cg.server = server:new {        box_cfg = {},        workdir = fio.cwd() .. '/tmp'    }    cg.server:start()end)g.after_each(function(cg)    cg.server:drop()    fio.rmtree(cg.server.workdir)end)g.test_indexes = function(cg)    cg.server:exec(function()        -- Create a space --        bands = box.schema.space.create('bands')        -- Specify field names and types --        box.space.bands:format({            { name = 'id', type = 'unsigned' },            { name = 'band_name', type = 'string' },            { name = 'year', type = 'unsigned' }        })        -- Create a primary index --        box.space.bands:create_index('primary', { parts = { 'id' } })        -- Create a unique secondary index --        box.space.bands:create_index('band', { parts = { 'band_name' } })        -- Create a non-unique secondary index --        box.space.bands:create_index('year', { parts = { { 'year' } }, unique = false })        -- Create a multi-part index --        box.space.bands:create_index('year_band', { parts = { { 'year' }, { 'band_name' } } })        -- Insert test data --        box.space.bands:insert { 1, 'Roxette', 1986 }        box.space.bands:insert { 2, 'Scorpions', 1965 }        box.space.bands:insert { 3, 'Ace of Base', 1987 }        box.space.bands:insert { 4, 'The Beatles', 1960 }        box.space.bands:insert { 5, 'Pink Floyd', 1965 }        box.space.bands:insert { 6, 'The Rolling Stones', 1962 }        box.space.bands:insert { 7, 'The Doors', 1965 }        box.space.bands:insert { 8, 'Nirvana', 1987 }        box.space.bands:insert { 9, 'Led Zeppelin', 1968 }        box.space.bands:insert { 10, 'Queen', 1970 }        -- Select a tuple by the specified primary key value --        select_primary = bands.index.primary:select { 1 }        --[[        ---        - - [1, 'Roxette', 1986]        ...        --]]        -- Select a tuple by the specified secondary key value --        select_secondary = bands.index.band:select { 'The Doors' }        --[[        ---        - - [7, 'The Doors', 1965]        ...        --]]        -- Select a tuple by the specified multi-part secondary key value --        select_multipart = bands.index.year_band:select { 1960, 'The Beatles' }        --[[        ---        - - [4, 'The Beatles', 1960]        ...        --]]        -- Select tuples by the specified partial key value --        select_multipart_partial = bands.index.year_band:select { 1965 }        --[[        ---        - - [5, 'Pink Floyd', 1965]          - [2, 'Scorpions', 1965]          - [7, 'The Doors', 1965]        ...        --]]        -- Select maximum 3 tuples by the specified secondary index --        select_limit = bands.index.band:select({}, { limit = 3 })        --[[        ---        - - [3, 'Ace of Base', 1987]          - [9, 'Led Zeppelin', 1968]          - [8, 'Nirvana', 1987]        ...        --]]        -- Select maximum 3 tuples with the key value greater than 1965 --        select_greater = bands.index.year:select({ 1965 }, { iterator = 'GT', limit = 3 })        --[[        ---        - - [9, 'Led Zeppelin', 1968]          - [10, 'Queen', 1970]          - [1, 'Roxette', 1986]        ...        --]]        -- Select maximum 3 tuples after the specified tuple --        select_after_tuple = bands.index.primary:select({}, { after = { 4, 'The Beatles', 1960 }, limit = 3 })        --[[        ---        - - [5, 'Pink Floyd', 1965]          - [6, 'The Rolling Stones', 1962]          - [7, 'The Doors', 1965]        ...        --]]        -- Select first 3 tuples and fetch a last tuple's position --        result, position = bands.index.primary:select({}, { limit = 3, fetch_pos = true })        -- Then, pass this position as the 'after' parameter --        select_after_position = bands.index.primary:select({}, { limit = 3, after = position })        --[[        ---        - - [4, 'The Beatles', 1960]          - [5, 'Pink Floyd', 1965]          - [6, 'The Rolling Stones', 1962]        ...        --]]        -- Tests --        t.assert_equals(select_primary[1], { 1, 'Roxette', 1986 })        t.assert_equals(select_secondary[1], { 7, 'The Doors', 1965 })        t.assert_equals(select_multipart[1], { 4, 'The Beatles', 1960 })        t.assert_equals(select_multipart_partial, { { 5, 'Pink Floyd', 1965 }, { 2, 'Scorpions', 1965 }, { 7, 'The Doors', 1965 } })        t.assert_equals(select_limit[1], { 3, 'Ace of Base', 1987 })        t.assert_equals(select_greater, { { 9, 'Led Zeppelin', 1968 }, { 10, 'Queen', 1970 }, { 1, 'Roxette', 1986 } })        t.assert_equals(select_after_tuple, { { 5, 'Pink Floyd', 1965 }, { 6, 'The Rolling Stones', 1962 }, { 7, 'The Doors', 1965 } })        t.assert_equals(select_after_position, { { 4, 'The Beatles', 1960 }, { 5, 'Pink Floyd', 1965 }, { 6, 'The Rolling Stones', 1962 } })    end)end
my_space:create_index('one_part_idx', {parts = {1, 'unsigned', is_nullable=true}})
-- с дополнительными фигурными скобкамиmy_space:create_index('one_part_idx', {parts = {{1, 'unsigned', is_nullable=true}}})-- без дополнительных фигурных скобокmy_space:create_index('one_part_idx', {parts = {1, 'unsigned', is_nullable=true}})

dimension

Размерность индекса RTREE.

Тип: number

Значение по умолчанию: 2

distance

Тип расстояния для индекса RTREE.

Тип: string

Значение по умолчанию: euclid

Возможные значения: euclid`, `manhattan

sequence

Создание генератора для индексов с использованием объекта последовательности. Подробнее см. в разделе использование последовательностей в create_index().

Тип: string или number

func

Указывает идентификатор функции функционального индекса.

Тип: string

hint

Начиная с: 2.6.1

Указывает, включена ли оптимизация hint для индекса TREE:

  • Если true, индекс работает быстрее.
  • Если false, размер индекса уменьшается вдвое.

Тип: boolean

Значение по умолчанию: true

bloom_fpr

Только для Vinyl

Указывает уровень ложных срабатываний фильтра Блума.

Тип: number

Значение по умолчанию: [vinyl.bloom_fpr](../../../configuration/configuration_reference#configuration_reference_vinyl_bloom_fpr)

page_size

Только для Vinyl

Указывает размер страницы, используемой для операций чтения и записи на диск.

Тип: number

Значение по умолчанию: [vinyl.page_size](../../../configuration/configuration_reference#configuration_reference_vinyl_page_size)

range_size

Только для Vinyl

Указывает максимальный размер диапазона по умолчанию (в байтах) для vinyl-индекса.

Тип: number

Значение по умолчанию: vinyl.range_size

run_count_per_level

Только для Vinyl

Указывает максимальное количество прогонов на уровень в LSM-дереве.

Тип: number

Значение по умолчанию: vinyl.run_count_per_level

run_size_ratio

Только для Vinyl

Указывает соотношение размеров различных уровней в LSM-дереве.

Тип: number

Значение по умолчанию: vinyl.run_size_ratio

layout

Только для MemCS

Указывает, как физически хранится колонка в индексе.

Возможные значения:

  • Если параметр не задан (или задан как plain), используется стандартный макет хранения plain.
  • Если задан как null_rle, используется кодирование повторов (RLE) для значений NULL. Применяется к nullable-колонкам, которые не перечислены в параметре parts индекса.

Например:

local format = {    { 'c1', 'unsigned' },    { 'c2', 'unsigned', is_nullable = true },    { 'c3', 'unsigned', is_nullable = true },    { 'c4', 'unsigned' },    { 'c5', 'unsigned', is_nullable = true },}box.schema.create_space('test', {    engine = 'memcs', format = format, field_count = #format})box.space.test:create_index('primary', {    parts = { 'c1' }, layout = 'null_rle'})box.space.test:create_index('secondary', {    parts = { 'c1', 'c2' }, covers = { 'c3', 'c4' }, layout = 'null_rle'})

В этом примере макет null_rle применяется к полям c2, c3, c5 в первичном индексе и к полю c3 во вторичном индексе.

Тип: string

Значение по умолчанию: не задано

covers

Только для MemCS

Указывает список неключевых полей, хранящихся в индексе (покрывающий индекс). covers также позволяет задать макет для отдельных колонок из числа покрытых полей.

Например:

box.space.test:create_index('sk', {    parts = {'c2', 'c3'},    covers = {        {'c4', layout = 'plain'},        {'c5', layout = 'null_rle'},    },})

В этом примере c4 хранится с использованием макета plain, а c5 — с использованием макета null_rle. Макеты, заданные в covers для отдельных колонок, имеют приоритет над параметром layout, который применяется ко всему индексу, а также над любым макетом, указанным в формате спейса.

Тип: table

Значение по умолчанию: не задано

key_part

Дескриптор одной части составного ключа. Таблица частей передается в параметр index_opts.parts.

field

Указывает номер или имя поля.

Тип: string или number

Примеры: Создание индекса с использованием имен и номеров полей

type

Указывает тип поля. Если тип поля указан в space_object:format(), key_part.type наследует это значение.

Тип: string

Значение по умолчанию: scalar

Возможные значения: перечислены в разделе Типы индексируемых полей

collation

Указывает правило сортировки, используемое для сравнения значений поля. Если правило сортировки поля указано в space_object:format(), key_part.collation наследует это значение.

Тип: string

Возможные значения: перечислены в системном спейсе box.space._collation

Пример:

-- Create a space --box.schema.space.create('tester')-- Use the 'unicode' collation --box.space.tester:create_index('unicode', { parts = { { field = 1,                                                        type = 'string',                                                        collation = 'unicode' } } })-- Use the 'unicode_ci' collation --box.space.tester:create_index('unicode_ci', { parts = { { field = 1,                                                        type = 'string',                                                        collation = 'unicode_ci' } } })-- Insert test data --box.space.tester:insert { 'ЕЛЕ' }box.space.tester:insert { 'елейный' }box.space.tester:insert { 'ёлка' }-- Returns nil --select_unicode = box.space.tester.index.unicode:select({ 'ЁлКа' })-- Returns 'ёлка' --select_unicode_ci = box.space.tester.index.unicode_ci:select({ 'ЁлКа' })

is_nullable

Указывает, может ли nil (или его эквивалент, например msgpack.NULL) использоваться в качестве значения поля. Если параметр is_nullable указан в space_object:format(), key_part.is_nullable наследует это значение.

Значение true для этого параметра можно задать, если:

  • тип индекса — TREE
  • индекс не является первичным

Также допускается не указывать значения для завершающих nullable-полей. В индексах такие null-значения всегда считаются равными другим null-значениям и всегда считаются меньшими, чем значения, отличные от null. Значения null могут встречаться многократно даже в уникальном индексе.

Тип: boolean

Значение по умолчанию: false

Пример:

box.space.tester:create_index('I', {unique = true, parts = {{field = 2, type = 'number', is_nullable = true}}})

exclude_null

Начиная с: 2.8.2

Указывает, может ли индекс пропускать кортежи со значением null в данной части ключа. Значение true для этого параметра можно задать, если:

  • тип индекса — TREE
  • индекс не является первичным

Если параметр exclude_null установлен в значение true, параметр is_nullable автоматически устанавливается в значение true. Обратите внимание, что этот параметр можно изменять динамически. В этом случае индекс перестраивается.

Тип: boolean

Значение по умолчанию: false

path

Указывает строку пути для поля, представляющего собой ассоциативный массив.

Тип: string

Примеры приведены ниже:

Примеры

Создание индекса с использованием имен и номеров полей

create_index() может использовать имена или номера полей для определения частей ключа.

Пример 1 (имена полей):

Чтобы создать часть ключа по имени поля, необходимо сначала вызвать space_object:format().

local fio = require('fio')local server = require('luatest.server')local t = require('luatest')local g = t.group()g.before_each(function(cg)    cg.server = server:new {        box_cfg = {},        workdir = fio.cwd() .. '/tmp'    }    cg.server:start()end)g.after_each(function(cg)    cg.server:drop()    fio.rmtree(cg.server.workdir)end)g.test_indexes = function(cg)    cg.server:exec(function()        -- Create a space --        bands = box.schema.space.create('bands')        -- Specify field names and types --        box.space.bands:format({            { name = 'id', type = 'unsigned' },            { name = 'band_name', type = 'string' },            { name = 'year', type = 'unsigned' }        })        -- Create a primary index --        box.space.bands:create_index('primary', { parts = { 'id' } })        -- Create a unique secondary index --        box.space.bands:create_index('band', { parts = { 'band_name' } })        -- Create a non-unique secondary index --        box.space.bands:create_index('year', { parts = { { 'year' } }, unique = false })        -- Create a multi-part index --        box.space.bands:create_index('year_band', { parts = { { 'year' }, { 'band_name' } } })        -- Insert test data --        box.space.bands:insert { 1, 'Roxette', 1986 }        box.space.bands:insert { 2, 'Scorpions', 1965 }        box.space.bands:insert { 3, 'Ace of Base', 1987 }        box.space.bands:insert { 4, 'The Beatles', 1960 }        box.space.bands:insert { 5, 'Pink Floyd', 1965 }        box.space.bands:insert { 6, 'The Rolling Stones', 1962 }        box.space.bands:insert { 7, 'The Doors', 1965 }        box.space.bands:insert { 8, 'Nirvana', 1987 }        box.space.bands:insert { 9, 'Led Zeppelin', 1968 }        box.space.bands:insert { 10, 'Queen', 1970 }        -- Select a tuple by the specified primary key value --        select_primary = bands.index.primary:select { 1 }        --[[        ---        - - [1, 'Roxette', 1986]        ...        --]]        -- Select a tuple by the specified secondary key value --        select_secondary = bands.index.band:select { 'The Doors' }        --[[        ---        - - [7, 'The Doors', 1965]        ...        --]]        -- Select a tuple by the specified multi-part secondary key value --        select_multipart = bands.index.year_band:select { 1960, 'The Beatles' }        --[[        ---        - - [4, 'The Beatles', 1960]        ...        --]]        -- Select tuples by the specified partial key value --        select_multipart_partial = bands.index.year_band:select { 1965 }        --[[        ---        - - [5, 'Pink Floyd', 1965]          - [2, 'Scorpions', 1965]          - [7, 'The Doors', 1965]        ...        --]]        -- Select maximum 3 tuples by the specified secondary index --        select_limit = bands.index.band:select({}, { limit = 3 })        --[[        ---        - - [3, 'Ace of Base', 1987]          - [9, 'Led Zeppelin', 1968]          - [8, 'Nirvana', 1987]        ...        --]]        -- Select maximum 3 tuples with the key value greater than 1965 --        select_greater = bands.index.year:select({ 1965 }, { iterator = 'GT', limit = 3 })        --[[        ---        - - [9, 'Led Zeppelin', 1968]          - [10, 'Queen', 1970]          - [1, 'Roxette', 1986]        ...        --]]        -- Select maximum 3 tuples after the specified tuple --        select_after_tuple = bands.index.primary:select({}, { after = { 4, 'The Beatles', 1960 }, limit = 3 })        --[[        ---        - - [5, 'Pink Floyd', 1965]          - [6, 'The Rolling Stones', 1962]          - [7, 'The Doors', 1965]        ...        --]]        -- Select first 3 tuples and fetch a last tuple's position --        result, position = bands.index.primary:select({}, { limit = 3, fetch_pos = true })        -- Then, pass this position as the 'after' parameter --        select_after_position = bands.index.primary:select({}, { limit = 3, after = position })        --[[        ---        - - [4, 'The Beatles', 1960]          - [5, 'Pink Floyd', 1965]          - [6, 'The Rolling Stones', 1962]        ...        --]]        -- Tests --        t.assert_equals(select_primary[1], { 1, 'Roxette', 1986 })        t.assert_equals(select_secondary[1], { 7, 'The Doors', 1965 })        t.assert_equals(select_multipart[1], { 4, 'The Beatles', 1960 })        t.assert_equals(select_multipart_partial, { { 5, 'Pink Floyd', 1965 }, { 2, 'Scorpions', 1965 }, { 7, 'The Doors', 1965 } })        t.assert_equals(select_limit[1], { 3, 'Ace of Base', 1987 })        t.assert_equals(select_greater, { { 9, 'Led Zeppelin', 1968 }, { 10, 'Queen', 1970 }, { 1, 'Roxette', 1986 } })        t.assert_equals(select_after_tuple, { { 5, 'Pink Floyd', 1965 }, { 6, 'The Rolling Stones', 1962 }, { 7, 'The Doors', 1965 } })        t.assert_equals(select_after_position, { { 4, 'The Beatles', 1960 }, { 5, 'Pink Floyd', 1965 }, { 6, 'The Rolling Stones', 1962 } })    end)end

Пример 2 (номера полей):

local fio = require('fio')local server = require('luatest.server')local t = require('luatest')local g = t.group()g.before_each(function(cg)    cg.server = server:new {        box_cfg = {},        workdir = fio.cwd() .. '/tmp'    }    cg.server:start()end)g.after_each(function(cg)    cg.server:drop()    fio.rmtree(cg.server.workdir)end)g.test_indexes = function(cg)    cg.server:exec(function()        -- Create a space --        bands = box.schema.space.create('bands')        -- Specify field names and types --        box.space.bands:format({            { name = 'id', type = 'unsigned' },            { name = 'band_name', type = 'string' },            { name = 'year', type = 'unsigned' }        })        -- Create a primary index --        box.space.bands:create_index('primary', { parts = { 1 } })        -- Create a unique secondary index --        box.space.bands:create_index('band', { parts = { 2 } })        -- Create a non-unique secondary index --        box.space.bands:create_index('year', { parts = { { 3 } }, unique = false })        -- Create a multi-part index --        box.space.bands:create_index('year_band', { parts = { 3, 2 } })        -- Insert test data --        box.space.bands:insert { 1, 'Roxette', 1986 }        box.space.bands:insert { 2, 'Scorpions', 1965 }        box.space.bands:insert { 3, 'Ace of Base', 1987 }        box.space.bands:insert { 4, 'The Beatles', 1960 }        box.space.bands:insert { 5, 'Pink Floyd', 1965 }        box.space.bands:insert { 6, 'The Rolling Stones', 1962 }        box.space.bands:insert { 7, 'The Doors', 1965 }        box.space.bands:insert { 8, 'Nirvana', 1987 }        box.space.bands:insert { 9, 'Led Zeppelin', 1968 }        box.space.bands:insert { 10, 'Queen', 1970 }        select_all = bands.index.band:select()        select_one = bands.index.year_band:select { 1960, 'The Beatles' }        select_limit = bands.index.year:select({ 1965 }, { iterator = 'GT', limit = 3 })        select_after_tuple = bands.index.primary:select({}, { after = { 4, 'The Beatles', 1960 }, limit = 3 })        result, position = bands.index.primary:select({}, { limit = 3, fetch_pos = true })        select_after_position = bands.index.primary:select({}, { limit = 3, after = position })        -- Tests --        t.assert_equals(select_all[1], { 3, 'Ace of Base', 1987 })        t.assert_equals(select_one[1], {4, 'The Beatles', 1960})        t.assert_equals(select_limit, { { 9, 'Led Zeppelin', 1968 }, { 10, 'Queen', 1970 }, { 1, 'Roxette', 1986 } })        t.assert_equals(select_after_tuple, { { 5, 'Pink Floyd', 1965 }, { 6, 'The Rolling Stones', 1962 }, { 7, 'The Doors', 1965 } })        t.assert_equals(select_after_position, { { 4, 'The Beatles', 1960 }, { 5, 'Pink Floyd', 1965 }, { 6, 'The Rolling Stones', 1962 } })    end)end

Создание индекса с использованием пути для полей с ассоциативными массивами (индексы по пути JSON)

Чтобы создать индекс для поля, которое представляет собой ассоциативный массив (строка с путем и скалярное значение), укажите строку c путем во время создания индекса:

parts = {{*{field-number}*}, {*{'data-type'}*}, path = {*{'path-name'}*}}

Тип индекса должен быть TREE или HASH, а содержимое поля — всегда ассоциативный массив с одним и тем же путем.

Пример 1 — Простое использование пути:

local fio = require('fio')local server = require('luatest.server')local t = require('luatest')local g = t.group()g.before_each(function(cg)    cg.server = server:new {        box_cfg = {},        workdir = fio.cwd() .. '/tmp'    }    cg.server:start()end)g.after_each(function(cg)    cg.server:drop()    fio.rmtree(cg.server.workdir)end)g.test_json_path_index = function(cg)    cg.server:exec(function()        box.schema.space.create('space1')        box.space.space1:create_index('primary', { parts = { { field = 1,                                                               type = 'scalar',                                                               path = 'age' } } })        box.space.space1:insert({ { age = 44 } })        box.space.space1:select(44)        box.schema.space.create('space2')        box.space.space2:format({ { 'id', 'unsigned' }, { 'data', 'map' } })        box.space.space2:create_index('info', { parts = { { 'data.full_name["firstname"]', 'str' },                                                          { 'data.full_name["surname"]', 'str' } } })        box.space.space2:insert({ 1, { full_name = { firstname = 'John', surname = 'Doe' } } })        box.space.space2:select { 'John' }        -- Tests --        json = require('json')        t.assert_equals(json.encode(box.space.space1:select(44)[1][1]), "{\"age\":44}")        t.assert_equals(json.encode(box.space.space2:select { 'John' }[1][2]), "{\"full_name\":{\"surname\":\"Doe\",\"firstname\":\"John\"}}")    end)end

Пример 2 — для большей наглядности используем path вместе с format() и JSON-синтаксисом:

local fio = require('fio')local server = require('luatest.server')local t = require('luatest')local g = t.group()g.before_each(function(cg)    cg.server = server:new {        box_cfg = {},        workdir = fio.cwd() .. '/tmp'    }    cg.server:start()end)g.after_each(function(cg)    cg.server:drop()    fio.rmtree(cg.server.workdir)end)g.test_json_path_index = function(cg)    cg.server:exec(function()        box.schema.space.create('space1')        box.space.space1:create_index('primary', { parts = { { field = 1,                                                               type = 'scalar',                                                               path = 'age' } } })        box.space.space1:insert({ { age = 44 } })        box.space.space1:select(44)        box.schema.space.create('space2')        box.space.space2:format({ { 'id', 'unsigned' }, { 'data', 'map' } })        box.space.space2:create_index('info', { parts = { { 'data.full_name["firstname"]', 'str' },                                                          { 'data.full_name["surname"]', 'str' } } })        box.space.space2:insert({ 1, { full_name = { firstname = 'John', surname = 'Doe' } } })        box.space.space2:select { 'John' }        -- Tests --        json = require('json')        t.assert_equals(json.encode(box.space.space1:select(44)[1][1]), "{\"age\":44}")        t.assert_equals(json.encode(box.space.space2:select { 'John' }[1][2]), "{\"full_name\":{\"surname\":\"Doe\",\"firstname\":\"John\"}}")    end)end

Создание индекса по массивам (multikey) с использованием опции path с символом [*]

Строка в параметре пути может содержать символ [*], который называется заменителем индекса массива. Описанные так индексы используются для JSON-документов, у которых одинаковая структура.

Например, при создании индекса по полю №2 для документа со строками, который будет начинаться с {'data': [{'name': '...'}, {'name': '...'}], раздел parts в запросе на создание индекса будет выглядеть так:

parts = {{field = 2, type = 'str', path = 'data[*].name'}}

Тогда кортежи с именами можно быстро получить с помощью index_object:select({key-value}).

Одно поле может содержать несколько ключей, как в этом примере, где один и тот же кортеж извлекается дважды, поскольку оба ключа — 'A' и 'B' — соответствуют запросу:

my_space = box.schema.space.create('json_documents')my_space:create_index('primary')multikey_index = my_space:create_index('multikey', {parts = {{field = 2, type = 'str', path = 'data[*].name'}}})my_space:insert({1,         {data = {{name = 'A'},                  {name = 'B'}},          extra_field = 1}})multikey_index:select({''}, {iterator = 'GE'})

Результат выборки будет выглядеть так:

tarantool> multikey_index:select({''},{iterator='GE'})---- - [1, {'data': [{'name': 'A'}, {'name': 'B'}], 'extra_field': 1}]- [1, {'data': [{'name': 'A'}, {'name': 'B'}], 'extra_field': 1}]...

Существуют следующие ограничения:

  • [*] должен использоваться отдельно или в конце имени в пути.

  • [*] не должен встречаться в пути дважды.

  • Если индекс содержит путь с x[*], ни один другой индекс не может содержать путь с x.component.

  • [*] не должен встречаться в пути первичного ключа.

  • Если для индекса задано unique=true и он содержит путь с [*], дубликаты ключей из разных кортежей запрещены, но дубликаты ключей в одном кортеже разрешены.

  • Значение поля должно иметь ту же структуру, что и в определении пути, либо быть nil (nil не индексируется).

  • В спейсе с многоключевыми индексами любой кортеж не может содержать более ~8000 элементов, проиндексированных таким образом.

Создание функционального индекса

Функциональные индексы — это индексы, которые вызывают пользовательскую функцию для формирования ключа индекса, в отличие от других типов индексов, где ключ формирует сам Tarantool. Функциональные индексы используют для сжатия, усечения или реверсирования или любого другого изменения индекса по желанию пользователя.

Ниже приведены рекомендации по созданию функциональных индексов:

  • Определение функции должно принимать кортеж, содержащий значения полей на момент запроса на изменение данных, и возвращать кортеж, содержащий значения, которые будут помещены в индекс.
  • Определение create_index должно включать спецификацию всех частей ключа, а пользовательская функция должна возвращать таблицу с тем же количеством частей ключа тех же типов.
  • Спейс должен использовать движок memtx.
  • Функция должна быть персистентной и детерминированной (см. Создание функции с телом).
  • Части ключа не должны зависеть от JSON-путей.
  • Функция должна обращаться к значениям частей ключа по индексу, а не по имени поля.
  • Функциональные индексы не могут быть индексами первичного ключа.
  • Функциональные индексы нельзя изменять, а функцию нельзя изменить, если она используется для индекса, поэтому единственный способ внести изменения — удалить индекс и создать его заново.
  • Для функциональных индексов подходят только изолированные функции.

Пример:

Функция может создать ключ, используя только первую букву строкового поля.

  1. Создайте спейс. В спейсе должно быть поле первичного ключа, отличное от поля, которое будет использоваться для функционального индекса:
local fio = require('fio')local server = require('luatest.server')local t = require('luatest')local g = t.group()g.before_each(function(cg)    cg.server = server:new {        box_cfg = {},        workdir = fio.cwd() .. '/tmp'    }    cg.server:start()end)g.after_each(function(cg)    cg.server:drop()    fio.rmtree(cg.server.workdir)end)g.test_func_index = function(cg)    cg.server:exec(function()        box.schema.space.create('tester')        box.space.tester:create_index('i', { parts = { { field = 1, type = 'string' } } })        function_code = [[function(tuple) return {string.sub(tuple[2],1,1)} end]]        box.schema.func.create('my_func',                { body = function_code, is_deterministic = true, is_sandboxed = true })        box.space.tester:create_index('func_index', { parts = { { field = 1, type = 'string' } },                                                      func = 'my_func' })        box.space.tester:insert({ 'a', 'wombat' })        box.space.tester:insert({ 'b', 'rabbit' })        box.space.tester.index.func_index:select('w')        box.space.tester.index.func_index:select(box.func.my_func:call({ { 'tester', 'wombat' } }))        -- Tests --        t.assert_equals(box.space.tester.index.func_index:select('w')[1], { 'a', 'wombat' })        t.assert_equals(box.space.tester.index.func_index:select(box.func.my_func:call({ { 'tester', 'wombat' } }))[1], { 'a', 'wombat' })    end)end
  1. Создайте функцию. Функция принимает кортеж. В этом примере она будет обрабатывать tuple[2], так как источником ключа является поле номер 2 во вставляемых данных. Используйте string.sub() из модуля string, чтобы получить первый символ:
local fio = require('fio')local server = require('luatest.server')local t = require('luatest')local g = t.group()g.before_each(function(cg)    cg.server = server:new {        box_cfg = {},        workdir = fio.cwd() .. '/tmp'    }    cg.server:start()end)g.after_each(function(cg)    cg.server:drop()    fio.rmtree(cg.server.workdir)end)g.test_func_index = function(cg)    cg.server:exec(function()        box.schema.space.create('tester')        box.space.tester:create_index('i', { parts = { { field = 1, type = 'string' } } })        function_code = [[function(tuple) return {string.sub(tuple[2],1,1)} end]]        box.schema.func.create('my_func',                { body = function_code, is_deterministic = true, is_sandboxed = true })        box.space.tester:create_index('func_index', { parts = { { field = 1, type = 'string' } },                                                      func = 'my_func' })        box.space.tester:insert({ 'a', 'wombat' })        box.space.tester:insert({ 'b', 'rabbit' })        box.space.tester.index.func_index:select('w')        box.space.tester.index.func_index:select(box.func.my_func:call({ { 'tester', 'wombat' } }))        -- Tests --        t.assert_equals(box.space.tester.index.func_index:select('w')[1], { 'a', 'wombat' })        t.assert_equals(box.space.tester.index.func_index:select(box.func.my_func:call({ { 'tester', 'wombat' } }))[1], { 'a', 'wombat' })    end)end
  1. Сделайте функцию персистентной с помощью функции box.schema.func.create:
local fio = require('fio')local server = require('luatest.server')local t = require('luatest')local g = t.group()g.before_each(function(cg)    cg.server = server:new {        box_cfg = {},        workdir = fio.cwd() .. '/tmp'    }    cg.server:start()end)g.after_each(function(cg)    cg.server:drop()    fio.rmtree(cg.server.workdir)end)g.test_func_index = function(cg)    cg.server:exec(function()        box.schema.space.create('tester')        box.space.tester:create_index('i', { parts = { { field = 1, type = 'string' } } })        function_code = [[function(tuple) return {string.sub(tuple[2],1,1)} end]]        box.schema.func.create('my_func',                { body = function_code, is_deterministic = true, is_sandboxed = true })        box.space.tester:create_index('func_index', { parts = { { field = 1, type = 'string' } },                                                      func = 'my_func' })        box.space.tester:insert({ 'a', 'wombat' })        box.space.tester:insert({ 'b', 'rabbit' })        box.space.tester.index.func_index:select('w')        box.space.tester.index.func_index:select(box.func.my_func:call({ { 'tester', 'wombat' } }))        -- Tests --        t.assert_equals(box.space.tester.index.func_index:select('w')[1], { 'a', 'wombat' })        t.assert_equals(box.space.tester.index.func_index:select(box.func.my_func:call({ { 'tester', 'wombat' } }))[1], { 'a', 'wombat' })    end)end
  1. Создайте функциональный индекс. Укажите поля, значения которых будут переданы функции. Укажите функцию:
local fio = require('fio')local server = require('luatest.server')local t = require('luatest')local g = t.group()g.before_each(function(cg)    cg.server = server:new {        box_cfg = {},        workdir = fio.cwd() .. '/tmp'    }    cg.server:start()end)g.after_each(function(cg)    cg.server:drop()    fio.rmtree(cg.server.workdir)end)g.test_func_index = function(cg)    cg.server:exec(function()        box.schema.space.create('tester')        box.space.tester:create_index('i', { parts = { { field = 1, type = 'string' } } })        function_code = [[function(tuple) return {string.sub(tuple[2],1,1)} end]]        box.schema.func.create('my_func',                { body = function_code, is_deterministic = true, is_sandboxed = true })        box.space.tester:create_index('func_index', { parts = { { field = 1, type = 'string' } },                                                      func = 'my_func' })        box.space.tester:insert({ 'a', 'wombat' })        box.space.tester:insert({ 'b', 'rabbit' })        box.space.tester.index.func_index:select('w')        box.space.tester.index.func_index:select(box.func.my_func:call({ { 'tester', 'wombat' } }))        -- Tests --        t.assert_equals(box.space.tester.index.func_index:select('w')[1], { 'a', 'wombat' })        t.assert_equals(box.space.tester.index.func_index:select(box.func.my_func:call({ { 'tester', 'wombat' } }))[1], { 'a', 'wombat' })    end)end
  1. Вставьте несколько кортежей. Выполните выборку, используя только первую букву — это сработает, так как она является ключом. Либо выполните выборку, используя ту же функцию, что и при вставке:
local fio = require('fio')local server = require('luatest.server')local t = require('luatest')local g = t.group()g.before_each(function(cg)    cg.server = server:new {        box_cfg = {},        workdir = fio.cwd() .. '/tmp'    }    cg.server:start()end)g.after_each(function(cg)    cg.server:drop()    fio.rmtree(cg.server.workdir)end)g.test_func_index = function(cg)    cg.server:exec(function()        box.schema.space.create('tester')        box.space.tester:create_index('i', { parts = { { field = 1, type = 'string' } } })        function_code = [[function(tuple) return {string.sub(tuple[2],1,1)} end]]        box.schema.func.create('my_func',                { body = function_code, is_deterministic = true, is_sandboxed = true })        box.space.tester:create_index('func_index', { parts = { { field = 1, type = 'string' } },                                                      func = 'my_func' })        box.space.tester:insert({ 'a', 'wombat' })        box.space.tester:insert({ 'b', 'rabbit' })        box.space.tester.index.func_index:select('w')        box.space.tester.index.func_index:select(box.func.my_func:call({ { 'tester', 'wombat' } }))        -- Tests --        t.assert_equals(box.space.tester.index.func_index:select('w')[1], { 'a', 'wombat' })        t.assert_equals(box.space.tester.index.func_index:select(box.func.my_func:call({ { 'tester', 'wombat' } }))[1], { 'a', 'wombat' })    end)end

Результаты обоих запросов select будут выглядеть так:

tarantool> box.space.tester.index.func_index:select('w')---- - ['a', 'wombat']...tarantool> box.space.tester.index.func_index:select(box.func.my_func:call({{'tester','wombat'}}));---- - ['a', 'wombat']...

Вот пример кода полностью:

local fio = require('fio')local server = require('luatest.server')local t = require('luatest')local g = t.group()g.before_each(function(cg)    cg.server = server:new {        box_cfg = {},        workdir = fio.cwd() .. '/tmp'    }    cg.server:start()end)g.after_each(function(cg)    cg.server:drop()    fio.rmtree(cg.server.workdir)end)g.test_func_index = function(cg)    cg.server:exec(function()        box.schema.space.create('tester')        box.space.tester:create_index('i', { parts = { { field = 1, type = 'string' } } })        function_code = [[function(tuple) return {string.sub(tuple[2],1,1)} end]]        box.schema.func.create('my_func',                { body = function_code, is_deterministic = true, is_sandboxed = true })        box.space.tester:create_index('func_index', { parts = { { field = 1, type = 'string' } },                                                      func = 'my_func' })        box.space.tester:insert({ 'a', 'wombat' })        box.space.tester:insert({ 'b', 'rabbit' })        box.space.tester.index.func_index:select('w')        box.space.tester.index.func_index:select(box.func.my_func:call({ { 'tester', 'wombat' } }))        -- Tests --        t.assert_equals(box.space.tester.index.func_index:select('w')[1], { 'a', 'wombat' })        t.assert_equals(box.space.tester.index.func_index:select(box.func.my_func:call({ { 'tester', 'wombat' } }))[1], { 'a', 'wombat' })    end)end

Функции для функциональных индексов могут возвращать множество ключей. Такие функции называют "мультиключевыми" (multikey).

Для создания мультиключевой функции параметры box.schema.func.create() должны включать is_multikey = true. Возвращаемое значение должно быть таблицей кортежей. Если мультиключевая функция возвращает N кортежей, в индекс будет добавлено N ключей.

Пример:

local fio = require('fio')local server = require('luatest.server')local t = require('luatest')local g = t.group()g.before_each(function(cg)    cg.server = server:new {        box_cfg = {},        workdir = fio.cwd() .. '/tmp'    }    cg.server:start()end)g.after_each(function(cg)    cg.server:drop()    fio.rmtree(cg.server.workdir)end)g.test_func_multikey_index = function(cg)    cg.server:exec(function()        tester = box.schema.space.create('withdata')        tester:format({ { name = 'name', type = 'string' },                        { name = 'address', type = 'string' } })        name_index = tester:create_index('name', { parts = { { field = 1, type = 'string' } } })        function_code = [[function(tuple)               local address = string.split(tuple[2])               local ret = {}               for _, v in pairs(address) do                 table.insert(ret, {utf8.upper(v)})               end               return ret             end]]        box.schema.func.create('address',                { body = function_code,                  is_deterministic = true,                  is_sandboxed = true,                  is_multikey = true })        addr_index = tester:create_index('addr', { unique = false,                                                   func = 'address',                                                   parts = { { field = 1, type = 'string',                                                          collation = 'unicode_ci' } } })        tester:insert({ "James", "SIS Building Lambeth London UK" })        tester:insert({ "Sherlock", "221B Baker St Marylebone London NW1 6XE UK" })        addr_index:select('Uk')        -- Tests --        t.assert_equals(addr_index:select('Uk'), {            { 'James', 'SIS Building Lambeth London UK' },            { 'Sherlock', '221B Baker St Marylebone London NW1 6XE UK' },        })    end)end