Indexing¶
AkasicDB text search indexes are defined with PostgreSQL's CREATE INDEX ... USING texton statement. Run the SQL on this page in a database where AkasicDB text search is available.
Note
A USING texton index requires the extension to be enabled in the target database. To use background worker features such as automatic batch commit, the server must be started with the extension registered in shared_preload_libraries. See Operations for details.
Create a shared example table.
Whole-Row Index¶
To search across an entire row, create a whole-row expression index. The @@ search operator uses a whole-row left operand, so docs @@ ... queries require this index.
CREATE INDEX docs_texton_idx
ON docs
USING texton ((docs.*))
WITH (mapping='{"fields":{"title":{"type":"text"},"body":{"type":"text"}}}');
A field mapping is required. CREATE INDEX is rejected if WITH (mapping='...') is omitted or fields is empty. Mapping syntax is covered in Field Mapping.
Rows already present at CREATE INDEX time are indexed immediately.
Field Mapping¶
Per-field type, analyzer, tokenizer, and dictionary, along with whether a field is available for sorting and aggregation, are declared in the JSON of the index option WITH (mapping='{...}'). A text field with no analyzer is indexed with the language-neutral standard analyzer. Tokenizer and dictionary selection is covered in Tokenization.
The examples on this page reuse a single index name, docs_texton_idx. From the second example on, each one is preceded by DROP INDEX IF EXISTS so that the page can be run top to bottom.
DROP INDEX IF EXISTS docs_texton_idx;
CREATE INDEX docs_texton_idx
ON docs
USING texton ((docs.*))
WITH (mapping='{"fields":{
"title":{"type":"text","analyzer":{"tokenizer":{"lindera":{"dictionary":"ko-dic"}}}},
"body":{"type":"text","analyzer":{"tokenizer":{"lindera":{"dictionary":"ko-dic"}}}}
}}');
| Mapping key | Description |
|---|---|
type |
The field type. Specifies the type of the indexed value, such as text, integer/float, or timestamptz. |
analyzer |
The tokenizer and dictionary for a text field. When omitted, the field is indexed with standard. |
fast |
Declares the field as available for sorting and aggregation. Sort keys for akasicdb.text_search() and aggregation fields for akasicdb.text_aggregate() must be fast. Sort keys carry one further type restriction (see Fast Fields). |
stored |
Retains the original field value so highlighting can build snippets. A field used with akasicdb.text_highlight() must be stored. |
Warning
A field mapping cannot be changed in place after the index is created. To change the mapping, recreate the index (use REINDEX to apply dictionary updates). See the reindex section in Tokenization.
Fast Fields¶
Declare fields used for sorting or aggregation as fast in the mapping. A field that is not fast cannot be used as a sort key for akasicdb.text_search() or as a target field for akasicdb.text_aggregate().
Sort keys carry one further restriction. Sorting supports only integer (i64 / u64), float (f64), bool, and date types, so text and keyword fields cannot be sort keys even when declared fast. Aggregation has no such restriction.
An analyzed text field is indexed as its individual tokens, so it does not work as a terms-aggregation key on its own — aggregating title would count morphemes, not titles. Declare an unanalyzed keyword sub-field alongside it and mark that fast. The sub-field is referenced with dot notation (title.raw).
DROP INDEX IF EXISTS docs_texton_idx;
CREATE INDEX docs_texton_idx
ON docs
USING texton ((docs.*))
WITH (mapping='{"fields":{
"title":{"type":"text",
"analyzer":{"tokenizer":{"lindera":{"dictionary":"ko-dic"}}},
"fields":{"raw":{"type":"keyword","fast":true}}},
"body":{"type":"text",
"analyzer":{"tokenizer":{"lindera":{"dictionary":"ko-dic"}}},
"stored":true}
}}');
With this mapping, title is searched with morphological analysis while title.raw holds the whole title as a single unanalyzed term for aggregation. body is declared stored so that highlighting can build snippets from it.
Multi-Column Index¶
To index specific columns instead of a whole-row expression, list the columns. Per-column mappings are declared with mapping, the same as for a whole-row index.
CREATE INDEX docs_title_body_texton_idx
ON docs
USING texton (title, body)
WITH (mapping='{"fields":{
"title":{"type":"text","analyzer":{"tokenizer":{"lindera":{"dictionary":"ko-dic"}}}},
"body":{"type":"text","analyzer":{"tokenizer":{"lindera":{"dictionary":"ko-dic"}}}}
}}');
A single column is listed the same way when only that column needs indexing.
CREATE INDEX docs_body_texton_idx
ON docs
USING texton (body)
WITH (mapping='{"fields":{
"body":{"type":"text","analyzer":{"tokenizer":{"lindera":{"dictionary":"ko-dic"}}}}
}}');
A column-list index does not provide the whole-row left operand that @@ requires, so docs @@ ... still needs the whole-row index above.
Partitioned Tables¶
Only a column-list index can be defined on a partitioned parent. The parent index fans out to each leaf partition, creating one index per partition.
The example table for this section:
CREATE TABLE docs_part (
id bigint,
title text,
body text
) PARTITION BY RANGE (id);
CREATE TABLE docs_part_p1 PARTITION OF docs_part FOR VALUES FROM (1) TO (1000);
CREATE TABLE docs_part_p2 PARTITION OF docs_part FOR VALUES FROM (1000) TO (2000);
CREATE INDEX docs_part_texton_idx
ON docs_part
USING texton (body)
WITH (mapping='{"fields":{
"body":{"type":"text","analyzer":{"tokenizer":{"lindera":{"dictionary":"ko-dic"}}}}
}}');
A whole-row expression cannot be used on the parent: USING texton ((docs_part.*)) on a partitioned parent is rejected with cannot convert whole-row table reference. To run @@ searches against a partitioned table, create a whole-row index on each leaf partition and search the leaf as the left operand rather than the parent.