space_object:create_index()
Параметры:
space_object(space_object) — ссылка на объектindex_name(string) — имя индекса, которое должно соответствовать правилам именования объектовindex_opts(table) — параметры индекса (см. 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
Параметры индекса, включающие имя индекса, тип, идентификаторы ключевых полей и так далее. Эти параметры передаются в метод space_object.create_index().
Тип: string
По умолчанию: TREE
Возможные значения: TREE`, `HASH`, `RTREE`, `BITSET
Уникальный числовой идентификатор индекса, который генерируется автоматически.
Тип: number
По умолчанию: ID последнего индекса + 1
Указывает, может ли индекс быть уникальным. Если значение 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
Указывает, следует ли игнорировать ошибку при попытке создать индекс с дублирующимся именем.
Тип: boolean
По умолчанию: false
Указывает ключевые части индекса.
Тип: таблица значений [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}})
Размерность индекса RTREE.
Тип: number
Значение по умолчанию: 2
Тип расстояния для индекса RTREE.
Тип: string
Значение по умолчанию: euclid
Возможные значения: euclid`, `manhattan
Создание генератора для индексов с использованием объекта последовательности. Подробнее см. в разделе использование последовательностей в create_index().
Тип: string или number
Указывает идентификатор функции функционального индекса.
Тип: string
Начиная с: 2.6.1
Указывает, включена ли оптимизация hint для индекса TREE:
- Если
true, индекс работает быстрее. - Если
false, размер индекса уменьшается вдвое.
Тип: boolean
Значение по умолчанию: true
Только для Vinyl
Указывает уровень ложных срабатываний фильтра Блума.
Тип: number
Значение по умолчанию: [vinyl.bloom_fpr](../../../configuration/configuration_reference#configuration_reference_vinyl_bloom_fpr)
Только для Vinyl
Указывает размер страницы, используемой для операций чтения и записи на диск.
Тип: number
Значение по умолчанию: [vinyl.page_size](../../../configuration/configuration_reference#configuration_reference_vinyl_page_size)
Только для Vinyl
Указывает максимальный размер диапазона по умолчанию (в байтах) для vinyl-индекса.
Тип: number
Значение по умолчанию: vinyl.range_size
Только для Vinyl
Указывает максимальное количество прогонов на уровень в LSM-дереве.
Тип: number
Значение по умолчанию: vinyl.run_count_per_level
Только для Vinyl
Указывает соотношение размеров различных уровней в LSM-дереве.
Тип: number
Значение по умолчанию: vinyl.run_size_ratio
Только для 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
Значение по умолчанию: не задано
Только для 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
Значение по умолчанию: не задано
Дескриптор одной части составного ключа. Таблица частей передается в параметр index_opts.parts.
Указывает номер или имя поля.
Тип: string или number
Примеры: Создание индекса с использованием имен и номеров полей
Указывает тип поля. Если тип поля указан в
space_object:format(), key_part.type наследует это
значение.
Тип: string
Значение по умолчанию: scalar
Возможные значения: перечислены в разделе
Типы индексируемых полей
Указывает правило сортировки, используемое для
сравнения значений поля. Если правило сортировки поля указано в
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({ 'ЁлКа' })
Указывает, может ли 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}}})
Начиная с: 2.8.2
Указывает, может ли индекс пропускать кортежи со значением null в данной
части ключа. Значение true для этого параметра можно задать, если:
- тип индекса — TREE
- индекс не является первичным
Если параметр exclude_null установлен в значение true, параметр
is_nullable автоматически устанавливается в значение true. Обратите
внимание, что этот параметр можно изменять динамически. В этом случае
индекс перестраивается.
Тип: boolean
Значение по умолчанию: false
Указывает строку пути для поля, представляющего собой ассоциативный массив.
Тип: string
Примеры приведены ниже:
- Создание индекса с использованием пути для полей с ассоциативными массивами (индексы по пути JSON)
- Создание индекса по массивам (multikey) с использованием опции path с символом [*]
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
Чтобы создать индекс для поля, которое представляет собой ассоциативный массив (строка с путем и скалярное значение), укажите строку 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
Строка в параметре пути может содержать символ [*], который называется
заменителем индекса массива. Описанные так индексы используются для
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-путей.
- Функция должна обращаться к значениям частей ключа по индексу, а не по имени поля.
- Функциональные индексы не могут быть индексами первичного ключа.
- Функциональные индексы нельзя изменять, а функцию нельзя изменить, если она используется для индекса, поэтому единственный способ внести изменения — удалить индекс и создать его заново.
- Для функциональных индексов подходят только изолированные функции.
Пример:
Функция может создать ключ, используя только первую букву строкового поля.
- Создайте спейс. В спейсе должно быть поле первичного ключа, отличное от поля, которое будет использоваться для функционального индекса:
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
- Создайте функцию. Функция принимает кортеж. В этом примере она будет
обрабатывать 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
- Сделайте функцию персистентной с помощью функции
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
- Создайте функциональный индекс. Укажите поля, значения которых будут переданы функции. Укажите функцию:
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
- Вставьте несколько кортежей. Выполните выборку, используя только первую букву — это сработает, так как она является ключом. Либо выполните выборку, используя ту же функцию, что и при вставке:
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) dotable.insert(ret, {utf8.upper(v)})endreturn retend]]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