Skip to main content

Arabic-aware search algorithm

This document describes the search behavior independently of C#, Entity Framework Core, and PostgreSQL. It can be used to reproduce the same behavior in another language or database. It reflects the implementation as of July 30, 2026. The algorithm is a pragmatic text search rather than a linguistic analyzer. It does not stem words, infer roots, transliterate Arabic, or correct arbitrary spelling mistakes. It expands a query into a bounded set of forms that account for:
  • an accidentally selected Arabic or QWERTY keyboard layout;
  • common Arabic and Persian forms of kaf and yeh;
  • alif maqsura;
  • terminal heh and taa marbuta;
  • case-insensitive substring matching;
  • weighted relevance for a primary field, normally a person’s full name.

Terminology

Unless stated otherwise, variation sets remove duplicate strings. The current implementation compares variation-set entries case-insensitively at the outer level and exactly at the inner Arabic/Persian expansion levels. A port may use one case-insensitive ordered set throughout if it produces the same observable matches.

High-level process

For ranked search:
  1. Trim the query and split it into terms.
  2. Generate variations for every term.
  3. Generate variations for the complete trimmed phrase.
  4. Keep a record if at least one term variation occurs in at least one searchable field.
  5. Compute an additive relevance score.
  6. Sort records by descending score.
  7. Apply an application-defined tie-breaker, such as descending record ID.
The primary field is also included in the set of searchable fields, even if the caller did not include it explicitly.

Query tokenization

Split the query on the ordinary space character (U+0020). Remove empty entries and trim each result. For example:
The canonical normalization operation described later can collapse other Unicode whitespace, but ranked search does not currently call it before tokenization. A compatible port should preserve that distinction.

Variation generation

Generate variations for a string in three stages.

1. Initial forms

Start with a case-insensitive set containing:
  1. the original input;
  2. the input interpreted as QWERTY keystrokes and converted to Arabic;
  3. the input interpreted as Arabic-layout keystrokes and converted to QWERTY.
Discard blank results.
This is keyboard-layout repair, not transliteration. For example, it is meant to recover text entered while the wrong keyboard layout was active.

2. Arabic and Persian character forms

For each initial form, produce whole-string styles for kaf:
For every kaf form, produce whole-string styles for yeh:
These are whole-string styles rather than every possible per-character combination. For example, a string containing several yeh characters gets an all-Arabic-yeh form and an all-Persian-yeh form, not every mixture of the two. This limits query growth.

3. Terminal heh and taa marbuta

Split each form into words. For every word ending in ة or ه, produce both endings while preserving the stem:
For a phrase, compute the Cartesian product of the alternatives for all its words, then join each combination with a single space.
The final variation set is the union of these expansions for all initial forms:

Keyboard-layout mapping

The mapping represents the Arabic keyboard layout used by the implementation. Conversion leaves characters that do not appear in the table unchanged. When converting Arabic to QWERTY:
  • recognize لا before processing individual characters and emit b;
  • treat Persian ک like Arabic ك;
  • treat Persian ی like Arabic ي.
When converting QWERTY to Arabic:
  • look up Latin letters in lowercase, so Caps Lock does not prevent repair;
  • map b to the two-character sequence لا;
  • preserve unmapped characters.

Matching

For a term, a record matches when any variation is contained in any searchable field:
The ranked search filter uses ANY semantics between terms:
Consequently, a query containing two terms retains records matching either term. Matching more terms affects ranking. The current PostgreSQL implementation uses ILIKE:
Other databases should use an equivalent case-insensitive operation. Exact case-folding behavior may depend on the database, collation, and locale.
Compatibility warning: The current implementation does not escape SQL pattern characters in the query. % and _ therefore act as wildcards under SQL LIKE/ILIKE semantics. A new implementation should either preserve this for strict compatibility or explicitly define and document escaping.
Null field values behave as non-matches.

Ranked relevance score

The score is additive. Every true condition contributes its points, including conditions that overlap. Let:
  • T0 be the first term;
  • Ti be a later term at index i;
  • P be the primary field;
  • F be all searchable fields, including P.
Add the following values: Each occurrence test means that at least one variation satisfies the condition. It does not count the number of matching variations or repeated occurrences in a field.
The first term is deliberately more influential than later terms. A primary field beginning with the first term should generally rank above a record that matches only later refinements elsewhere. Because scoring is additive, a full-phrase prefix normally also receives the full-phrase contains points and several term-level points. This stacking is intentional.

Unranked filtering variant

The implementation also defines an unranked filter. It generates the same per-term variations and supports two ways to combine terms:
  • strict mode: every term must match at least one searchable field;
  • loose mode: at least one term must match at least one searchable field.
An additional caller-supplied predicate is always combined with the search predicate using logical AND.
If no usable search condition exists, apply only the additional predicate. If neither exists, return the input unchanged.

Canonical text normalization

Canonical normalization is a separate utility. The query-variation algorithm does not currently invoke it automatically. Apply these steps in order:
  1. Return an empty or whitespace-only input trimmed.
  2. Apply Unicode compatibility normalization, NFKC.
  3. Normalize Arabic characters to their Persian forms:
    • ك to ک;
    • ي to ی;
    • ى to ی.
  4. Remove Unicode non-spacing marks and tatweel (ـ).
  5. Apply the following mappings:
  1. Replace every Unicode whitespace run with one ordinary space.
  2. Trim leading and trailing spaces.
There are also two narrower conversions:

Complexity and limits

For each initial form, character expansion produces at most three kaf styles and four yeh styles. Terminal ه/ة alternatives can double for every affected word. Before duplicate removal, the upper bound is approximately:
where E is the number of words ending in ه or ة. For ordinary single-term searches the set stays small, and duplicate removal usually reduces it substantially. Long phrases with many affected endings can grow exponentially. Implementations should set reasonable query-length and term-count limits, or cap generated variations. Database predicate count grows approximately with:
Implementations should inspect generated queries and add appropriate database indexes. Leading-wildcard substring searches such as %value% usually cannot use a normal B-tree index efficiently.

Compatibility checklist

A port should test at least:
  • empty and whitespace-only queries;
  • Arabic and Persian kaf: ك and ک;
  • Arabic yeh, Persian yeh, and alif maqsura: ي, ی, and ى;
  • terminal ه and ة;
  • Arabic text typed with a QWERTY layout selected, and the reverse;
  • Caps Lock during QWERTY-to-Arabic repair;
  • the لا two-character mapping;
  • null searchable fields;
  • one-term and multi-term candidate filtering;
  • overlapping additive ranking conditions;
  • records tied on relevance score;
  • % and _ behavior if the backend uses SQL patterns;
  • canonical normalization of diacritics, tatweel, joiners, whitespace, and Arabic-script digits.

Worked examples

These examples combine the individual rules into complete inputs and outputs. Variation lists labeled “selected” are illustrative subsets because keyboard-layout expansion can add forms that are not relevant to the point being demonstrated.

Canonical normalization

The result is:

Wrong keyboard layout

If the user intended to enter احمد but had the QWERTY layout selected, the physical keystrokes appear as hpl].
The repair works in the other direction as well:
This does not translate either word. It reinterprets the same physical keys under the other keyboard layout.

Arabic and Persian forms

For the input علي, the selected variations include:
For the input فاطمة, terminal-ending expansion includes:
A stored value using any of these Arabic/Persian forms can therefore satisfy the same contains condition.

Multiword ranking

Assume the query is:
Search fullName and address, with fullName as the primary field: All three records pass the candidate filter:
  • A contains both terms in the primary field.
  • B contains both terms in the primary field, but in the reverse order.
  • C contains علي in the primary field and احمد in another searchable field.
Their scores are: The resulting order is:
This example also shows that conditions stack. Record A receives both the full-phrase prefix and full-phrase contains points, while a match in the primary field also counts as a match in any searchable field.

Strict and loose filtering

Using the same records and the query احمد علي:
If record C instead had address = "كربلاء", it would match only علي: