Use SAI text analyzers with CQL

You can use Cassandra Query Language (CQL) to create analyzed indexes on text columns, and then use the analyzer operator (:) to run term matching queries against the analyzed text columns. Term matching finds rows where the search term appears anywhere within the analyzed text field.

To enable the analyzer operator, create a Storage Attached Indexing (SAI) index with a non-tokenizing filter or index_analyzer mapping in the WITH OPTIONS clause.

Index analyzer configuration options

SAI uses the Apache Lucene™ Java Analyzer API to transform text columns into tokens for indexing and querying which can use built-in or custom analyzers.

The analyzer determines how a column’s values will be analyzed before indexing occurs, and then the analyzed index stores values derived from the raw column values. The stored values are dependent on the analyzer configuration options, which include tokenization, filtering, and charFiltering. When the index is used in a query, the analyzer is applied to the query term.

To configure an analyzed index, you must specify the index_analyzer option in the WITH OPTIONS clause. The value of index_analyzer is either:

  • A single string that selects a built-in analyzer, which is a preconfigured set of tokenizers and filters. For example:

    { "index_analyzer": "standard" }
  • A JSON object that defines a custom analyzer with a tokenizer, optional filters and charFilters, and any other fields required to configure the analyzer. For example:

    {
      "index_analyzer": {
        "tokenizer": {
          "name": "ngram",
          "args": {
            "minGramSize": "2",
            "maxGramSize": "3"
            }
          },
        "filters": [ { "name": "lowercase" } ]
      }
    }

There are many analyzers, tokenizers, and filters from the Apache Lucene™ project (version 9.8.0) that you can use in your index_analyzer configuration.

Type Usage Values

Analyzers

index_analyzer string

  • standard (default): Filters StandardTokenizer output that divides text into terms on word boundaries and then uses the LowerCaseFilter.

  • simple: Filters LetterTokenizer output that divides text into terms whenever it encounters a character which is not a letter and then uses the LowerCaseFilter.

  • whitespace: Uses WhitespaceTokenizer to divide text into terms whenever it encounters any whitespace character.

  • stop: Filters LetterTokenizer output with LowerCaseFilter and removes the default English stop words as set by Lucene.

  • lowercase: Normalizes input by applying LowerCaseFilter (no additional tokenization is performed).

  • keyword: Uses KeywordTokenizer, which is an identity function ("noop") on input values and tokenizes the entire input as a single token.

  • <language>: Language-specific analyzer: Arabic, Armenian, Basque, Bengali, Brazilian, Bulgarian, Catalan, CJK, Czech, Danish, Dutch, English, Estonian, Finnish, French, Galician, German, Greek, Hindi, Hungarian, Indonesian, Irish, Italian, Latvian, Lithuanian, Norwegian, Persian, Portuguese, Romanian, Russian, Sorani, Spanish, Swedish, Thai, Turkish

Tokenizers

tokenizer object in index_analyzer object

standard, classic, keyword, letter, nGram, edgeNGram, pathHierarchy, pattern, simplePattern, simplePatternSplit, thai, uax29UrlEmail, whitespace, wikipedia

CharFilters

char_filters array in index_analyzer object

cjk, htmlstrip, mapping, persian, patternreplace

TokenFilters

filters array in index_analyzer object

apostrophe, wordDelimiterGraph, portugueseLightStem, latvianStem, dropIfFlagged, keepWord, indicNormalization, bengaliStem, turkishLowercase, galicianStem, bengaliNormalization, portugueseMinimalStem, galicianMinimalStem, swedishMinimalStem, stop, limitTokenCount, italianLightStem, wordDelimiter, teluguStem, hungarianLightStem, protectedTerm, lowercase, capitalization, hyphenatedWords, type, keywordMarker, frenchMinimalStem, kStem, swedishLightStem, soraniNormalization, commonGramsQuery, numericPayload, persianStem, limitTokenOffset, hunspellStem, soraniStem, czechStem, norwegianMinimalStem, englishMinimalStem, norwegianLightStem, germanMinimalStem, snowballPorter, removeDuplicates, minHash, keywordRepeat, germanNormalization, dictionaryCompoundWord, synonymGraph, englishPossessive, spanishMinimalStem, fixedShingle, patternTyping, classic, frenchLightStem, trim, indonesianStem, spanishPluralStem, hindiStem, scandinavianFolding, delimitedBoost, commonGrams, reverseString, cjkWidth, fingerprint, finnishLightStem, greekStem, porterStem, limitTokenPosition, persianNormalization, typeAsSynonym, patternReplace, tokenOffsetPayload, codepointCount, bulgarianStem, synonym, germanStem, asciiFolding, decimalDigit, Word2VecSynonym, scandinavianNormalization, russianLightStem, serbianNormalization, elision, portugueseStem, arabicNormalization, length, greekLowercase, concatenateGraph, flattenGraph, fixBrokenOffsets, truncate, cjkBigram, brazilianStem, uppercase, nGram, dateRecognizer, teluguNormalization, shingle, norwegianNormalization, hindiNormalization, delimitedPayload, spanishLightStem, stemmerOverride, patternCaptureGroup, hyphenationCompoundWord, germanLightStem, edgeNGram, typeAsPayload, irishLowercase, delimitedTermFrequency, arabicStem

For more information and examples, see Create SAI index and SAI examples.

Non-tokenizing filter options

The non-tokenizing filters are case_sensitive, normalize, and ascii. These options cannot be combined with index_analyzer in the same index. However, you can configure multiple non-tokenizing filters on the same index for a chained pipeline of non-tokenizing filters.

For example, the following index applies case sensitivity first, then normalization, then Ascii.

CREATE CUSTOM INDEX commenter_case_sensitive_idx ON cycling.comments_vs (commenter)
  USING 'StorageAttachedIndex'
  WITH OPTIONS = { 'case_sensitive': true, 'normalize': true, 'ascii': true};

Non-tokenizing filters also use the analyzer operator when queried.

For explanations of the filters, see SAI options.

Analyzer operator

After creating an analyzed SAI index, use the analyzer operator (:) in your CQL queries, similar to other operators:

SELECT * FROM animals.sharks
  WHERE analyzed_column : 'search_term';

The analyzer operator has the following restrictions:

  • Only SAI indexes support the : operator.

  • The : operator requires an index_analyzer configured on the SAI index.

  • The analyzed column cannot be part of the Primary Key, including the partition key and clustering columns.

  • The : operator can be used with only SELECT statements.

  • The : operator cannot be used with light-weight transactions, such as a condition for an IF clause.

SAI queries recognize both : and = operators, but = is deprecated for SAI indexes because it can be confused with the equality operator (also represented by =) for non-analyzed columns. By design, queries on analyzed indexes cannot be as deterministic as true equality matches on non-analyzed columns because the analyzed index processes column values according to the analyzer configuration.

Example: Analyzer term matching

  1. Create a table:

    CREATE TABLE default_keyspace.products
    (
      id text PRIMARY KEY,
      val text
    );
  2. Create an SAI index with the index_analyzer option and stemming enabled:

    CREATE CUSTOM INDEX default_keyspace_products_val_idx
      ON default_keyspace.products(val)
      USING 'org.apache.cassandra.index.sai.StorageAttachedIndex' WITH OPTIONS = {
      'index_analyzer': '{
        "tokenizer" : {"name" : "standard"},
        "filters" : [{"name" : "porterstem"}]
    }'};
  3. Insert sample rows:

    INSERT INTO default_keyspace.products (id, val)
      VALUES ('1', 'soccer cleats');
    
    INSERT INTO default_keyspace.products (id, val)
      VALUES ('2', 'running shoes');
    
    INSERT INTO default_keyspace.products (id, val)
      VALUES ('3', 'hiking shoes');
  4. Query to retrieve your data:

    Query on a single term
    SELECT * FROM default_keyspace.products
      WHERE val : 'running';
    Query on two separate terms

    In this case, term matching searches each condition sequentially to find rows that contain both terms.

    SELECT * FROM default_keyspace.products
      WHERE val : 'hiking' AND val : 'shoes';
    Query on a phrase

    In this case, term matching attempts to retrieve the complete phrase.

    SELECT * FROM default_keyspace.products
      WHERE val : 'soccer cleats';

You can query analyzed indexes directly, or combine them with vector searches (ANN OF) to combine term matching with semantic similarity for more focused and contextually relevant results. Similarity search requires an indexed vector column.

For example, if you generate a vector embedding from the phrase "tell me about available shoes", you can use a vector search to get a list of rows with similar vectors. These rows will likely correlate with shoe-related strings.

SELECT * from products
  ORDER BY vector ANN OF [6.0,2.0, ... 3.1,4.0]
  LIMIT 10;

Alternatively, you can filter these search results by a specific keyword, such as hiking:

SELECT * from products
  WHERE val : 'hiking'
  ORDER BY vector ANN OF [6.0,2.0, … 3.1,4.0]
  LIMIT 10;

Was this helpful?

Give Feedback

How can we improve the documentation?

© Copyright IBM Corporation 2026 | Privacy policy | Terms of use |  Manage Privacy Choices

Apache, Apache Cassandra, Cassandra, Apache Tomcat, Tomcat, Apache Lucene, Apache Solr, Apache Hadoop, Hadoop, Apache Pulsar, Pulsar, Apache Spark, Spark, Apache TinkerPop, TinkerPop, Apache Kafka and Kafka are either registered trademarks or trademarks of the Apache Software Foundation or its subsidiaries in Canada, the United States and/or other countries. Kubernetes is the registered trademark of the Linux Foundation.

General Inquiries: Contact IBM