BM25 Stemmer Plugins¶
NornicDB's BM25 analyzer is language-neutral by default: NFKC normalization, Unicode case folding, Unicode token splitting, and exact token matching. The database does not ship with French, Spanish, Dutch, Chinese, Ukrainian, or any other language stemmer enabled or bundled.
When you need stemming, install a trusted local Go plugin and select it by ID for the database that owns the corpus. NornicDB keeps the runtime contract small: the server loads manifest-verified plugins, selects one stemmer ID per database, and rebuilds BM25 whenever the analyzer fingerprint changes.
What Ships¶
nornicdbloads stemmer plugins from a configured directory.nornicdb-snowballpackages generated Snowball Go source as a NornicDB stemmer plugin.- No Snowball algorithms, Snowball runtime, or language stemmers are embedded in the server binary.
nornicdb-adminis not part of this workflow and does not depend on Snowball.
Go plugins are supported on Unix-like platforms where Go supports -buildmode=plugin. On Windows, keep bm25_stemmer set to none.
Build The Packaging Tool¶
Build the standalone helper from this repository:
Build or install the upstream Snowball compiler separately. The official Snowball project provides the compiler, algorithm files, and Go runtime package at github.com/snowballstem/snowball/go.
git clone https://github.com/snowballstem/snowball.git ./third_party/snowball
git -C ./third_party/snowball checkout v3.1.1
make -C ./third_party/snowball
The examples use the current stable Snowball v3.1.1 compiler and Go runtime. Keep both on the same release so generated code and runtime APIs stay aligned. The plugin version passed to nornicdb-snowball should identify that build.
Package A Snowball Algorithm¶
This example packages the official French Snowball algorithm. The same pattern works for Spanish and Dutch.
mkdir -p ./build/stemmers/french ./plugins/stemmers
cd ./build/stemmers/french
go mod init example.com/acme/nornicdb-stemmers/french
go get github.com/snowballstem/snowball/go@v3.1.1+incompatible
../../../third_party/snowball/snowball \
../../../third_party/snowball/algorithms/french.sbl \
-go \
-P main \
-goruntime github.com/snowballstem/snowball/go \
-o stemmer
go mod tidy
go mod vendor
cd ../../..
./bin/nornicdb-snowball package \
--language french \
--id snowball.french \
--version 3.1.1 \
--module ./build/stemmers/french \
--source ./build/stemmers/french/stemmer.go \
--output ./plugins/stemmers/snowball-french.so
The package command writes two files:
Keep both files together. The manifest records the plugin ID, ABI version, language label, library filename, entrypoint symbol, and SHA-256 digest.
Language Examples¶
Use the official Snowball algorithm files where they exist:
| Language | Snowball source file | Plugin ID | Output |
|---|---|---|---|
| French | third_party/snowball/algorithms/french.sbl | snowball.french | plugins/stemmers/snowball-french.so |
| Spanish | third_party/snowball/algorithms/spanish.sbl | snowball.spanish | plugins/stemmers/snowball-spanish.so |
| Dutch | third_party/snowball/algorithms/dutch.sbl | snowball.dutch | plugins/stemmers/snowball-dutch.so |
For example, Spanish only changes the directory, source file, ID, language label, and output name:
./bin/nornicdb-snowball package \
--language spanish \
--id snowball.spanish \
--version 3.1.1 \
--module ./build/stemmers/spanish \
--source ./build/stemmers/spanish/stemmer.go \
--output ./plugins/stemmers/snowball-spanish.so
Chinese and Ukrainian are not official upstream Snowball algorithms in the published Snowball language set. A real deployment for either language must vendor a vetted Snowball .sbl algorithm from your own source or from a third-party source you have reviewed. Package it the same way:
./third_party/snowball/snowball \
./local-algorithms/ukrainian.sbl \
-go \
-P main \
-goruntime github.com/snowballstem/snowball/go \
-o ./build/stemmers/ukrainian/stemmer
./bin/nornicdb-snowball package \
--language ukrainian \
--id snowball.ukrainian \
--version local-2026.09 \
--module ./build/stemmers/ukrainian \
--source ./build/stemmers/ukrainian/stemmer.go \
--output ./plugins/stemmers/snowball-ukrainian.so
./third_party/snowball/snowball \
./local-algorithms/chinese.sbl \
-go \
-P main \
-goruntime github.com/snowballstem/snowball/go \
-o ./build/stemmers/chinese/stemmer
./bin/nornicdb-snowball package \
--language chinese \
--id snowball.chinese \
--version local-2026.09 \
--module ./build/stemmers/chinese \
--source ./build/stemmers/chinese/stemmer.go \
--output ./plugins/stemmers/snowball-chinese.so
Chinese search often needs segmentation more than suffix stemming. NornicDB's current BM25 tokenizer remains the built-in Unicode tokenizer; a Chinese stemmer plugin can normalize individual tokens, but it does not replace tokenization.
Configure NornicDB¶
Set the process-level plugin directory and the default stemmer ID:
The equivalent environment variables are:
export NORNICDB_STEMMER_PLUGINS_DIR=./plugins/stemmers
export NORNICDB_SEARCH_BM25_STEMMER=snowball.french
Use none to keep the default analyzer:
You can also select stemmers per database. Per-database settings win over the global default:
databases:
french_docs:
db.nornic.search.bm25.stemmer: snowball.french
spanish_docs:
db.nornic.search.bm25.stemmer: snowball.spanish
default_docs:
db.nornic.search.bm25.stemmer: none
At runtime, update one database through the admin API:
curl -X PUT http://localhost:7474/admin/databases/french_docs/config \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"overrides": {
"db.nornic.search.bm25.stemmer": "snowball.french"
}
}'
The update rebuilds that database's BM25 service from original stored text. If the selected stemmer ID is unavailable or the rebuild fails, the update fails and the previous search service stays active.
Runtime Contract¶
- The selected plugin must be present at startup in
NORNICDB_STEMMER_PLUGINS_DIRorplugins.stemmers.directory. - The plugin ID is an operator-facing identifier such as
snowball.french, not a filesystem path. - A configured missing plugin is a startup/configuration error. NornicDB does not silently fall back to
none. - Go plugins cannot be unloaded. Add, remove, or replace plugin files with a process restart.
- Changing stemmer ID, plugin version, ABI version, digest, tokenizer, BM25 format, or indexed property projection forces a BM25 rebuild.
- Phrase search remains literal against the stored text and does not invoke the stemmer.