Advanced morphology
Morphology preprocessors can be applied to the words being indexed to replace different forms of the same word with the base, normalized form or improve segmentation. For instance, English stemmer will normalize both “dogs” and “dog” to “dog”, making search results for the both keywords the same.
There are 4 different morphology preprocessors that Manticore implements.
- Lemmatizer reduces a keyword form to a so-called lemma, a proper normal form, or in other words, a valid natural language root word. For example, “running” could be reduced to “run”, the infinitive verb form, and “octopi” would be reduced to “octopus”, the singular noun form. Note that sometimes a word form can have multiple corresponding root words. For instance, by looking at “dove” it is not possible to tell whether this is a past tense of “dive” the verb as in “He dove into a pool.”, or “dove” the noun as in “White dove flew over the cuckoo’s nest.” In this case lemmatizer can generate all the possible root forms.
- Stemmer reduces a keyword form to a so-called stem by removing and/or replacing certain well-known suffixes. The resulting stem is however not guaranteed to be a valid word on itself. For instance, with a Porter English stemmers “running” would still reduce to “run”, which is fine, but “business” would reduce to “busi”, which is not a word, and “octopi” would not reduce at all. Stemmers are essentially (much) simpler but still pretty good replacements of full-blown lemmatizers.
- Phonetic algorithms replace the words with specially crafted phonetic codes that are equal even when the words original are different, but phonetically close.
- Word breaking algorithms split text into words. Currently available only for Chinese.
morphology
morphology = morphology1[, morphology2, ...]
A list of morphology preprocessors to apply. Optional, default is empty (do not apply any preprocessor).
The morphology processors that come with our own built-in Manticore implementations are:
- English, Russian, and German lemmatizers
- English, Russian, Arabic, and Czech stemmers
- SoundEx and MetaPhone phonetic algorithms
- Chinese word breaking algorithm
- Snowball (libstemmer) stemmers for more than 15 other languages.
Lemmatizers require dictionary .pak
files that you can download from the website. The dictionaries needs to be put in the directory specified by lemmatizer_base. Also, there is the lemmatizer_cache setting which lets you speed up lemmatizing (and therefore indexing) by spending more RAM for, basically, an uncompressed dictionary cache.
The Chinese language segmentation using ICU is also available. It is a much more precise, but a little bit slower way (compared to n-grams) to segment Chinese documents. charset_table must contain all Chinese characters (you can use alias “cjk”). In case of “morphology=icu_chinese” documents are first pre-processed by ICU, then the result is processed by the tokenizer (according to your charset_table) and then other morphology processors specified in the “morphology” option are applied. When the documents are processed by ICU, only those parts of texts that contain Chinese are passed to ICU for segmentation, others can be modified by other means (different morphologies, charset_table etc.)
Built-in English and Russian stemmers should be faster than their libstemmer counterparts, but can produce slightly different results, because they are based on an older version.
Soundex implementation matches that of MySQL. Metaphone implementation is based on Double Metaphone algorithm and indexes the primary code.
Built-in values that are available for use in the morphology
option are as follows:
- none - do not perform any morphology processing
- lemmatize_ru - apply Russian lemmatizer and pick a single root form
- lemmatize_uk - apply Ukrainian lemmatizer and pick a single root form (install it first in Centos or Ubuntu/Debian). For correct work of the lemmatizer make sure specific Ukrainian characters are preserved in your
charset_table
since by default they are not. For that override them, like this:charset_table='non_cjk,U+0406->U+0456,U+0456,U+0407->U+0457,U+0457,U+0490->U+0491,U+0491'
. Here is an interactive course about how to install and use the urkainian lemmatizer. - lemmatize_en - apply English lemmatizer and pick a single root form
- lemmatize_de - apply German lemmatizer and pick a single root form
- lemmatize_ru_all - apply Russian lemmatizer and index all possible root forms
- lemmatize_uk_all - apply Ukrainian lemmatizer and index all possible root forms. Find the installation links above and take care of the
charset_table
. - lemmatize_en_all - apply English lemmatizer and index all possible root forms
- lemmatize_de_all - apply German lemmatizer and index all possible root forms
- stem_en - apply Porter’s English stemmer
- stem_ru - apply Porter’s Russian stemmer
- stem_enru - apply Porter’s English and Russian stemmers
- stem_cz - apply Czech stemmer
- stem_ar - apply Arabic stemmer
- soundex - replace keywords with their SOUNDEX code
- metaphone - replace keywords with their METAPHONE code
- icu_chinese - apply Chinese text segmentation using ICU
- libstemmer_* . Refer to the list of supported languages for details
Several stemmers can be specified at once comma-separated. They will be applied to incoming words in the order they are listed, and the processing will stop once one of the stemmers actually modifies the word. Also when wordforms feature is enabled the word will be looked up in word forms dictionary first, and if there is a matching entry in the dictionary, stemmers will not be applied at all. Or in other words, wordforms can be used to implement stemming exceptions.
- SQL
- JSON
- PHP
- Python
- javascript
- Java
- CONFIG
SQL JSON PHP Python javascript Java CONFIG
CREATE TABLE products(title text, price float) morphology = 'stem_en, libstemmer_sv'
POST /cli -d "CREATE TABLE products(title text, price float) morphology = 'stem_en, libstemmer_sv'"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'float']
],[
'morphology' => 'stem_en, libstemmer_sv'
]);
utilsApi.sql('CREATE TABLE products(title text, price float) morphology = \'stem_en, libstemmer_sv\'')
res = await utilsApi.sql('CREATE TABLE products(title text, price float) morphology = \'stem_en, libstemmer_sv\'');
utilsApi.sql("CREATE TABLE products(title text, price float) morphology = 'stem_en, libstemmer_sv'");
table products {
morphology = stem_en, libstemmer_sv
type = rt
path = tbl
rt_field = title
rt_attr_uint = price
}
morphology_skip_fields
morphology_skip_fields = field1[, field2, ...]
A list of fields to skip morphology preprocessing. Optional, default is empty (apply preprocessors to all fields).
- SQL
- JSON
- PHP
- Python
- javascript
- Java
- CONFIG
SQL JSON PHP Python javascript Java CONFIG
CREATE TABLE products(title text, name text, price float) morphology_skip_fields = 'name' morphology = 'stem_en'
POST /cli -d "
CREATE TABLE products(title text, name text, price float) morphology_skip_fields = 'name' morphology = 'stem_en'"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'float']
],[
'morphology_skip_fields' => 'name',
'morphology' => 'stem_en'
]);
utilsApi.sql('CREATE TABLE products(title text, price float) morphology_skip_fields = \'name\' morphology = \'stem_en\'')
res = await utilsApi.sql('CREATE TABLE products(title text, price float) morphology_skip_fields = \'name\' morphology = \'stem_en\'');
utilsApi.sql("CREATE TABLE products(title text, price float) morphology_skip_fields = 'name' morphology = 'stem_en'");
table products {
morphology_skip_fields = name
morphology = stem_en
type = rt
path = tbl
rt_field = title
rt_field = name
rt_attr_uint = price
}
min_stemming_len
min_stemming_len = length
Minimum word length at which to enable stemming. Optional, default is 1 (stem everything).
Stemmers are not perfect, and might sometimes produce undesired results. For instance, running “gps” keyword through Porter stemmer for English results in “gp”, which is not really the intent. min_stemming_len
feature lets you suppress stemming based on the source word length, ie. to avoid stemming too short words. Keywords that are shorter than the given threshold will not be stemmed. Note that keywords that are exactly as long as specified will be stemmed. So in order to avoid stemming 3-character keywords, you should specify 4 for the value. For more finely grained control, refer to wordforms feature.
- SQL
- JSON
- PHP
- Python
- javascript
- Java
- CONFIG
SQL JSON PHP Python javascript Java CONFIG
CREATE TABLE products(title text, price float) min_stemming_len = '4' morphology = 'stem_en'
POST /cli -d "
CREATE TABLE products(title text, price float) min_stemming_len = '4' morphology = 'stem_en'"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'float']
],[
'min_stemming_len' => '4',
'morphology' => 'stem_en'
]);
utilsApi.sql('CREATE TABLE products(title text, price float) min_stemming_len = \'4\' morphology = \'stem_en\'')
res = await utilsApi.sql('CREATE TABLE products(title text, price float) min_stemming_len = \'4\' morphology = \'stem_en\'');
utilsApi.sql("CREATE TABLE products(title text, price float) min_stemming_len = '4' morphology = 'stem_en'");
table products {
min_stemming_len = 4
morphology = stem_en
type = rt
path = tbl
rt_field = title
rt_attr_uint = price
}
index_exact_words
index_exact_words = {0|1}
Whether to index the original keywords along with the stemmed/remapped versions. Optional, default is 0 (do not index).
When enabled, index_exact_words
forces indexation to put the raw keywords in the full-text index along with the stemmed versions. That, in turn, enables exact form operator in the query language to work. This impacts the full-text index size and the indexing time. However, searching performance is not impacted at all.
- SQL
- JSON
- PHP
- Python
- javascript
- Java
- CONFIG
SQL JSON PHP Python javascript Java CONFIG
CREATE TABLE products(title text, price float) index_exact_words = '1' morphology = 'stem_en'
POST /cli -d "
CREATE TABLE products(title text, price float) index_exact_words = '1' morphology = 'stem_en'"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'float']
],[
'index_exact_words' => '1',
'morphology' => 'stem_en'
]);
utilsApi.sql('CREATE TABLE products(title text, price float) index_exact_words = \'1\' morphology = \'stem_en\'')
res = await utilsApi.sql('CREATE TABLE products(title text, price float) index_exact_words = \'1\' morphology = \'stem_en\'');
utilsApi.sql("CREATE TABLE products(title text, price float) index_exact_words = '1' morphology = 'stem_en'");
table products {
index_exact_words = 1
morphology = stem_en
type = rt
path = tbl
rt_field = title
rt_attr_uint = price
}
Advanced HTML tokenization
Stripping HTML tags
html_strip
html_strip = {0|1}
Whether to strip HTML markup from incoming full-text data. Optional, default is 0. Known values are 0 (disable stripping) and 1 (enable stripping).
Both HTML tags and entities and considered markup and get processed.
HTML tags are removed, their contents (i.e., everything between <p>
and </p>
) are left intact by default. You can choose to keep and index attributes of the tags (e.g., HREF attribute in an A tag, or ALT in an IMG one). Several well-known inline tags are completely removed, all other tags are treated as block level and replaced with whitespace. For example, te<strong>st</strong>
text will be indexed as a single keyword ‘test’, however, te<p>st</p>
will be indexed as two keywords ‘te’ and ‘st’. Known inline tags are as follows: A, B, I, S, U, BASEFONT, BIG, EM, FONT, IMG, LABEL, SMALL, SPAN, STRIKE, STRONG, SUB, SUP, TT.
HTML entities get decoded and replaced with corresponding UTF-8 characters. Stripper supports both numeric forms (such as ï
) and text forms (such as ó
or
). All entities as specified by HTML4 standard are supported.
Stripping should work with properly formed HTML and XHTML, but, just as most browsers, may produce unexpected results on malformed input (such as HTML with stray <'s
or unclosed >'s
).
Only the tags themselves, and also HTML comments, are stripped. To strip the contents of the tags too (eg. to strip embedded scripts), see html_remove_elements option. There are no restrictions on tag names; ie. everything that looks like a valid tag start, or end, or a comment will be stripped.
- SQL
- JSON
- PHP
- Python
- javascript
- Java
- CONFIG
SQL JSON PHP Python javascript Java CONFIG
CREATE TABLE products(title text, price float) html_strip = '1'
POST /cli -d "
CREATE TABLE products(title text, price float) html_strip = '1'"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'float']
],[
'html_strip' => '1'
]);
utilsApi.sql('CREATE TABLE products(title text, price float) html_strip = \'1\'')
res = await utilsApi.sql('CREATE TABLE products(title text, price float) html_strip = \'1\'');
utilsApi.sql("CREATE TABLE products(title text, price float) html_strip = '1'");
table products {
html_strip = 1
type = rt
path = tbl
rt_field = title
rt_attr_uint = price
}
html_index_attrs
html_index_attrs = img=alt,title; a=title;
A list of markup attributes to index when stripping HTML. Optional, default is empty (do not index markup attributes).
Specifies HTML markup attributes whose contents should be retained and indexed even though other HTML markup is stripped. The format is per-tag enumeration of indexable attributes, as shown above.
- SQL
- JSON
- PHP
- Python
- javascript
- Java
- CONFIG
SQL JSON PHP Python javascript Java CONFIG
CREATE TABLE products(title text, price float) html_index_attrs = 'img=alt,title; a=title;' html_strip = '1'
POST /cli -d "
CREATE TABLE products(title text, price float) html_index_attrs = 'img=alt,title; a=title;' html_strip = '1'"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'float']
],[
'html_index_attrs' => 'img=alt,title; a=title;',
'html_strip' => '1'
]);
utilsApi.sql('CREATE TABLE products(title text, price float) html_index_attrs = \'img=alt,title; a=title;\' html_strip = \'1\'')
res = await utilsApi.sql('CREATE TABLE products(title text, price float) html_index_attrs = \'img=alt,title; a=title;\' html_strip = \'1\'');
utilsApi.sql("CREATE TABLE products(title text, price float) html_index_attrs = \'img=alt,title; a=title;\' html_strip = '1'");
table products {
html_index_attrs = img=alt,title; a=title;
html_strip = 1
type = rt
path = tbl
rt_field = title
rt_attr_uint = price
}
html_remove_elements
html_remove_elements = element1[, element2, ...]
A list of HTML elements for which to strip contents along with the elements themselves. Optional, default is empty string (do not strip contents of any elements).
This feature allows to strip element contents, ie. everything that is between the opening and the closing tags. It is useful to remove embedded scripts, CSS, etc. Short tag form for empty elements (ie. <br/>
) is properly supported; ie. the text that follows such tag will not be removed.
The value is a comma-separated list of element (tag) names whose contents should be removed. Tag names are case insensitive.
- SQL
- JSON
- PHP
- Python
- javascript
- Java
- CONFIG
SQL JSON PHP Python javascript Java CONFIG
CREATE TABLE products(title text, price float) html_remove_elements = 'style, script' html_strip = '1'
POST /cli -d "
CREATE TABLE products(title text, price float) html_remove_elements = 'style, script' html_strip = '1'"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'float']
],[
'html_remove_elements' => 'style, script',
'html_strip' => '1'
]);
utilsApi.sql('CREATE TABLE products(title text, price float) html_remove_elements = \'style, script\' html_strip = \'1\'')
res = await utilsApi.sql('CREATE TABLE products(title text, price float) html_remove_elements = \'style, script\' html_strip = \'1\'');
utilsApi.sql("CREATE TABLE products(title text, price float) html_remove_elements = \'style, script\' html_strip = '1'");
table products {
html_remove_elements = style, script
html_strip = 1
type = rt
path = tbl
rt_field = title
rt_attr_uint = price
}
Extracting important parts from HTML
index_sp
index_sp = {0|1}
Whether to detect and index sentence and paragraph boundaries. Optional, default is 0 (do not detect and index).
This directive enables sentence and paragraph boundary indexing. It’s required for the SENTENCE and PARAGRAPH operators to work. Sentence boundary detection is based on plain text analysis, so you only need to set index_sp = 1
to enable it. Paragraph detection is however based on HTML markup, and happens in the HTML stripper. So to index paragraph locations you also need to enable the stripper by specifying html_strip = 1
. Both types of boundaries are detected based on a few built-in rules enumerated just below.
Sentence boundary detection rules are as follows.
- Question and exclamation signs (? and !) are always a sentence boundary.
- Trailing dot (.) is a sentence boundary, except:
- When followed by a letter. That’s considered a part of an abbreviation (as in “S.T.A.L.K.E.R” or “Goldman Sachs S.p.A.”).
- When followed by a comma. That’s considered an abbreviation followed by a comma (as in “Telecom Italia S.p.A., founded in 1994”).
- When followed by a space and a small letter. That’s considered an abbreviation within a sentence (as in “News Corp. announced in February”).
- When preceded by a space and a capital letter, and followed by a space. That’s considered a middle initial (as in “John D. Doe”).
Paragraph boundaries are inserted at every block-level HTML tag. Namely, those are (as taken from HTML 4 standard) ADDRESS, BLOCKQUOTE, CAPTION, CENTER, DD, DIV, DL, DT, H1, H2, H3, H4, H5, LI, MENU, OL, P, PRE, TABLE, TBODY, TD, TFOOT, TH, THEAD, TR, and UL.
Both sentences and paragraphs increment the keyword position counter by 1.
- SQL
- JSON
- PHP
- Python
- javascript
- Java
- CONFIG
SQL JSON PHP Python javascript Java CONFIG
CREATE TABLE products(title text, price float) index_sp = '1' html_strip = '1'
POST /cli -d "
CREATE TABLE products(title text, price float) index_sp = '1' html_strip = '1'"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'float']
],[
'index_sp' => '1',
'html_strip' => '1'
]);
utilsApi.sql('CREATE TABLE products(title text, price float) index_sp = \'1\' html_strip = \'1\'')
res = await utilsApi.sql('CREATE TABLE products(title text, price float) index_sp = \'1\' html_strip = \'1\'');
utilsApi.sql("CREATE TABLE products(title text, price float) index_sp = \'1\' html_strip = '1'");
table products {
index_sp = 1
html_strip = 1
type = rt
path = tbl
rt_field = title
rt_attr_uint = price
}
index_zones
index_zones = h*, th, title
A list of in-field HTML/XML zones to index. Optional, default is empty (do not index zones).
Zones can be formally defined as follows. Everything between an opening and a matching closing tag is called a span, and the aggregate of all spans corresponding sharing the same tag name is called a zone. For instance, everything between the occurrences of <H1>
and </H1>
in the document field belongs to H1 zone.
Zone indexing, enabled by index_zones
directive, is an optional extension of the HTML stripper. So it will also require that the stripper is enabled (with html_strip = 1
). The value of the index_zones
should be a comma-separated list of those tag names and wildcards (ending with a star) that should be indexed as zones.
Zones can nest and overlap arbitrarily. The only requirement is that every opening tag has a matching tag. You can also have an arbitrary number of both zones (as in unique zone names, such as H1) and spans (all the occurrences of those H1 tags) in a document. Once indexed, zones can then be used for matching with the ZONE operator, see extended_query_syntax.
- SQL
- JSON
- PHP
- Python
- javascript
- Java
- CONFIG
SQL JSON PHP Python javascript Java CONFIG
CREATE TABLE products(title text, price float) index_zones = 'h, th, title' html_strip = '1'
POST /cli -d "
CREATE TABLE products(title text, price float) index_zones = 'h, th, title' html_strip = '1'"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'float']
],[
'index_zones' => 'h*,th,title',
'html_strip' => '1'
]);
utilsApi.sql('CREATE TABLE products(title text, price float) index_zones = \'h, th, title\' html_strip = \'1\'')
res = await utilsApi.sql('CREATE TABLE products(title text, price float) index_zones = \'h, th, title\' html_strip = \'1\'');
utilsApi.sql("CREATE TABLE products(title text, price float) index_zones = 'h, th, title' html_strip = '1'");
table products {
index_zones = h*, th, title
html_strip = 1
type = rt
path = tbl
rt_field = title
rt_attr_uint = price
}
Creating a distributed table
Manticore supports distributed tables. They look like usual plain or real-time tables, but internally they are just a ‘proxy’, or named collection of another child tables used for actual searching. When a query is directed at such table, it is distributed among all tables in the collection. Server collects responses of the queries and processes them as necessary:
- applies sorting
- recalculates final values of aggregates, etc
From the client’s standpoint it looks transparent, as if you just queried any single table.
Distributed tables can be composed from any other tables fitting your requirements
- local storage tables (plain table, Real-Time)
- remote tables
- combined local storage and remote tables
- percolate tables (local, remote and combinations)
- single local and several remotes or any other combinations
Nesting distributed tables is supported by declaring them with agent
(even if they are on the same machine). Distributed tables cannot be declared with local
and they will be ignored.
Percolate and template tables should not be mixed with plain and/or RT tables.
Distributed table is defined by type ‘distributed’ in the configuration file or via SQL clause CREATE TABLE
In a configuration file
table foo {
type = distributed
local = bar
local = bar1, bar2
agent = 127.0.0.1:9312:baz
agent = host1|host2:tbl
agent = host1:9301:tbl1|host2:tbl2 [ha_strategy=random retry_count=10]
...
}
Via SQL
CREATE TABLE distributed_index type='distributed' local='local_index' agent='127.0.0.1:9312:remote_index'
Children
Either way the key component of a distributed table is a list of children (the tables it points to).
- Lines, starting with
local =
enumerate local tables, served in the same server. Several local tables may be written as severallocal =
lines, or combined into one list, separated by commas. - Lines, starting with
agent =
enumerate remote tables, served anywhere. Each line represents one agent, or endpoint.
Each agent can include several external locations and options specifying how to work with them.
Note that for remotes the server knows nothing about the type of the table, and it may cause errors, if, say, you issue CALL PQ
to remote ‘foo’ which is not a percolate table.