Repository navigation
Document native full-text search #691
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
87 commits
Select commit
Hold shift + click to select a range
1782051
Document native full-text search
nizzlenitz 02dbfe3
Clarify full-text prefix result limits
nizzlenitz 5bb4233
docs(fulltext): clarify runtime and recovery contracts
nizzlenitz b5e9b21
docs(fulltext): address final review
nizzlenitz 639e80c
docs(fulltext): clarify operational behavior
nizzlenitz b7ad217
docs(release): keep patch releases at heading level two
nizzlenitz e3379b8
docs(fulltext): define query edge cases
nizzlenitz 0954d11
docs(fulltext): resolve final contract gaps
nizzlenitz 6855bed
docs(fulltext): align with field-only schema
nizzlenitz 1d3792d
docs(fulltext): address review feedback
nizzlenitz 2fd2564
docs(fulltext): align operational caveats
nizzlenitz 5334c2a
docs(fulltext): complete security and storage guidance
nizzlenitz e50fc56
docs(fulltext): clarify REST count behavior
nizzlenitz 2c4ab6d
docs(fulltext): correct runtime behavior
nizzlenitz 8bbaa43
docs(fulltext): align Blob troubleshooting
nizzlenitz 25599cb
docs(fulltext): clarify search iteration
nizzlenitz 505a706
Document full-text filter metadata
nizzlenitz a035305
Merge remote-tracking branch 'origin/main' into docs/full-text-search
nizzlenitz 0109329
Document full-text snapshot continuation
nizzlenitz 8b08091
Clarify continuation callback and shared capacity requirements
nizzlenitz 06a5a9b
Document continuation transport sizes and joint admission limits
nizzlenitz 6923c72
Clarify close acknowledgement and completed-page metadata
nizzlenitz 3fbaced
Explain HEAD behavior for full-text continuation
nizzlenitz 8ea7e2c
Document acknowledged continuation completion after cleanup
nizzlenitz e29315e
Explain continuation cleanup timing and filtering costs
nizzlenitz 9dc5dc5
Document continuation method restrictions
nizzlenitz 8dee012
Clarify supported continuation methods
nizzlenitz f23ba40
Clarify continuation availability and response handling
nizzlenitz ebfebdb
Clarify continuation context and HTTP exceptions
nizzlenitz 48411ee
Complete continuation examples and retry guidance
nizzlenitz c7e263e
Link continuation from configuration and REST references
nizzlenitz f04f3e6
Tighten paging references and callback guidance
nizzlenitz e22a0e6
Show copyable native runtime capacity settings
nizzlenitz df34b72
Merge remote-tracking branch 'origin/main' into kyle/docs-fulltext-coβ¦
nizzlenitz b995dbe
Place continuation version badges consistently
nizzlenitz 66dfb17
Document explicit full-text paging and ordinary search budgets
nizzlenitz 88964e2
Make paging examples enforce caller permissions and complete the API β¦
nizzlenitz ffb08f7
Clarify conservative paging reservations and version availability
nizzlenitz fa1fb2a
Document paging permission boundaries and upgrade work limits
nizzlenitz 238315f
Clarify explicit paging dispatch and permanent worker initialization β¦
nizzlenitz 41b831e
Clarify paging permissions, failure recovery and application startup
nizzlenitz fb873fc
Use plain language for copied paging responses
nizzlenitz f9ebdcd
Clarify paging response envelopes and managed worker routing
nizzlenitz 1c4d88f
Describe measured token sizes plainly
nizzlenitz 06d3089
Clarify continuation entry points and native runtime recovery
nizzlenitz aec291e
Document full-text limit and offset paging
nizzlenitz abcd092
Clarify full-text query syntax and budget recovery
nizzlenitz ada02a4
Describe exact and estimated full-text counts accurately
nizzlenitz 9643499
Make paging prerequisites and operating limits discoverable
nizzlenitz cf7eba9
Use the table global in the paging example
nizzlenitz 12fdb83
Link the pinned native query limits
nizzlenitz 83cc98f
Clarify paging count and configuration exceptions
nizzlenitz c7f3a79
Keep paging guidance in existing reference sections
nizzlenitz f5f9a01
Document independent full-text result and work budgets
nizzlenitz 0444a14
Clarify query rejection and documentation pointers
nizzlenitz a5f332a
Account for bounded replay in the raw-hit budget
nizzlenitz 481dfb0
Make full-text failure recovery and native bounds explicit
nizzlenitz af0ee1a
Clarify both request ceilings and link storage tuning guidance
nizzlenitz 518f4ff
Match the documented audit default to the shipped configuration
nizzlenitz 72f5e4e
Use existing worker restarts to apply full-text request limits
nizzlenitz ee1862d
Clarify full-text admission limits and error recovery
nizzlenitz ab2ae97
Clarify filtered full-text paging and partial-page behavior
nizzlenitz a95ce00
Clarify full-text configuration leaf updates
nizzlenitz afabe8d
Clarify the pre-v5.4 prefix paging boundary
nizzlenitz 4d7df3f
Reconcile native full-text guidance with v5.4 paging
nizzlenitz ee4436e
Remove unsupported full-text metadata examples and reconcile release β¦
nizzlenitz d0754c2
Clarify native response overflow and audit requirements
nizzlenitz 1c86ef0
Keep structured-filter guidance focused on supported options
nizzlenitz c319f8c
Clarify paging recovery, offset costs and condition ownership
nizzlenitz a629e79
Make paging errors explicit and keep badge styling unchanged
nizzlenitz df5264e
Clarify worker error codes and native capacity recovery
nizzlenitz 06b8ff0
docs: address native full-text review feedback
nizzlenitz a047778
docs: clarify streaming errors and REST range semantics
nizzlenitz 51713cc
docs: tighten full-text count guidance and scope
nizzlenitz f1f55da
docs: address paging restart and error review feedback
nizzlenitz d9f1b2e
Merge main into native full-text documentation
nizzlenitz b5294b0
Merge main into full-text paging documentation
nizzlenitz e92602c
Clarify full-text replica freshness and operational limits
nizzlenitz 2c084ec
Document Tantivy 0.27 versions and rebuild requirements
nizzlenitz 966b0fb
Document full-text engine upgrades and recovery
nizzlenitz bbf78c0
Clarify full-text upgrade readiness and retry guidance
nizzlenitz 47e6ae4
Clarify streamed search failures and upgrade requirements
nizzlenitz cc220b7
Integrate search reference companion and centralize upgrade guidance
nizzlenitz 3640af2
Scope freshness waits and unsupported downgrade guidance
nizzlenitz 48d547b
Include final search reference wording
nizzlenitz 94bb9a2
Integrate current documentation main and clarify upgrade errors
nizzlenitz 58456b3
Preserve current references and finish engine upgrade guidance
nizzlenitz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,193 @@ | ||
| --- | ||
| title: Add Full-Text Search to a Product Catalog | ||
| description: Configure and query a BM25-ranked product search in Harper. | ||
| --- | ||
|
|
||
| # Add Full-Text Search to a Product Catalog | ||
|
|
||
| <VersionBadge version="v5.3.0" /> | ||
|
|
||
| This guide adds relevance-ranked text search to a product catalog, then exposes it through Harper's JavaScript and REST APIs. | ||
|
|
||
| ## What you will build | ||
|
|
||
| - A BM25-ranked index over product names, descriptions, and tags | ||
| - A Harper application endpoint with highlights and a structured category filter | ||
| - REST queries for terms, phrases, and record autocomplete | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - Harper 5.3 or later using RocksDB | ||
| - A component with a GraphQL schema, JavaScript resources, and `rest: true` in its `config.yaml` | ||
|
|
||
| ## 1. Declare the index | ||
|
|
||
| Declare an `@fullText` index over the fields customers search on a RocksDB-backed table. Ordinary tables inherit enabled audit logging by default, but a new full-text table requires explicit `audit: true` for its transaction-log recovery source. See the [audit requirement](/reference/v5/full-text-search/configuration) for existing tables. | ||
|
|
||
| ```graphql title="schema.graphql" | ||
| type Product @table(database: "catalog", audit: true) @export { | ||
| id: ID @primaryKey | ||
| name: String | ||
| description: String | ||
| tags: [String] | ||
| category: String @indexed | ||
| price: Float @indexed | ||
| available: Boolean | ||
| catalogSearch: FullText | ||
| @fullText( | ||
| fields: [ | ||
| { name: "name", weight: 3, highlight: true } | ||
| { name: "description", weight: 1, highlight: true } | ||
| { name: "tags" } | ||
| ] | ||
| highlighting: { maxFragments: 2, fragmentLength: 120 } | ||
| ) | ||
| } | ||
| ``` | ||
|
|
||
| The query-only field name `catalogSearch` identifies the index. It is not stored with each product. `weight: 3` makes a match in `name` contribute more to BM25 relevance than the same match in `description` or `tags`. Highlighting is enabled only for the fields shown to customers. | ||
|
|
||
| The `@indexed` declarations create ordinary Harper secondary indexes. Full-text searches can use them to narrow candidates, and Harper checks structured conditions against current product records before returning results. | ||
|
|
||
| ## 2. Add records | ||
|
|
||
| Use the generated REST endpoint: | ||
|
|
||
| ```http | ||
| POST /Product/ | ||
| Content-Type: application/json | ||
|
|
||
| { | ||
| "id": "shoe-1", | ||
| "name": "Waterproof trail running shoe", | ||
| "description": "Lightweight shoe for wet mountain trails", | ||
| "tags": ["outdoor", "trail"], | ||
| "category": "footwear", | ||
| "price": 129, | ||
| "available": true | ||
| } | ||
| ``` | ||
|
|
||
| Harper commits the record first, then applies the committed change to the local derived full-text index. Each replica performs the same derivation after it has received both the full-text schema declaration and the record transactions. | ||
|
|
||
| If the index remains behind for more than 30 seconds, a local write can return retryable `503` code `DERIVED_INDEX_LAGGING`. Retry writes with backoff while the index catches up; freshness options apply only to searches. See [write backpressure](/reference/v5/full-text-search/overview#write-backpressure). | ||
|
|
||
| ## 3. Search from a Harper application | ||
|
|
||
| ```javascript title="resources.js" | ||
| export class SearchProducts extends Resource { | ||
| static async post(_target, data, context) { | ||
| if (!context?.user) { | ||
| const error = new Error('Authentication required'); | ||
| error.statusCode = 401; | ||
| throw error; | ||
| } | ||
| const body = (await data) ?? {}; | ||
| const text = typeof body.q === 'string' ? body.q.trim() : ''; | ||
| if (!text) { | ||
| const error = new Error('q is required'); | ||
| error.statusCode = 400; | ||
| throw error; | ||
| } | ||
| const conditions = [ | ||
| { | ||
| attribute: 'catalogSearch', | ||
| comparator: 'matches', | ||
| value: text, | ||
| fields: ['name', 'description', 'tags'], | ||
|
kylebernhardy marked this conversation as resolved.
Resolved
|
||
| }, | ||
| { attribute: 'available', comparator: 'equals', value: true }, | ||
| ]; | ||
| const category = body.category; | ||
| if (category) conditions.push({ attribute: 'category', comparator: 'equals', value: category }); | ||
| return databases.catalog.Product.search( | ||
| { | ||
| conditions, | ||
| select: ['id', 'name', 'price', '$score', '$highlights'], | ||
| limit: 20, | ||
| checkPermission: true, | ||
| }, | ||
| context | ||
| ); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Call the resource with an authenticated request: | ||
|
|
||
| ```http | ||
| POST /SearchProducts/ | ||
| Content-Type: application/json | ||
|
|
||
| { "q": "trail", "category": "footwear" } | ||
| ``` | ||
|
|
||
| This custom resource accepts a structured JSON search body through POST. The category filter is optional; `q` is required. For URL-based GET searches, use the exported table endpoint in step 4. | ||
|
|
||
| Harper applies the availability and optional category conditions while accepting current product records. Existing secondary indexes can narrow candidates; rejected records do not count toward the requested page. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minor β "Rejected records do not count toward the requested page" has no version qualifier, but the tutorial is badged v5.3.0 and the reference says this only holds from v5.4. |
||
|
|
||
| Calls through `tables` and `databases` use Harper's [trusted server-side context](/reference/v5/components/javascript-environment#tables). Forwarding the resource's `context` preserves the authenticated caller, and `checkPermission: true` asks the table to enforce that caller's table and source-field permissions. The custom resource must do both. Highlights can expose searched source text even when those fields are absent from `select`. | ||
|
|
||
| Matches in `tags` contribute to search results and ranking, but `$highlights` does not include tag matches because `tags` is not configured with `highlight: true`. | ||
|
|
||
| ## 4. Search through REST | ||
|
|
||
| The exported table supports the same match comparator: | ||
|
|
||
| ```http | ||
| GET /Product/?catalogSearch=matches=waterproof%20trail&category=footwear&select(id,name,price,$score,$highlights)&limit(20) | ||
| ``` | ||
|
|
||
| For a phrase: | ||
|
|
||
| ```http | ||
| GET /Product/?catalogSearch=matches_phrase=trail%20running&limit(20) | ||
| ``` | ||
|
|
||
| For record autocomplete: | ||
|
|
||
| ```http | ||
| GET /Product/?catalogSearch=matches_prefix=waterproof%20tra&select(id,name)&limit(10) | ||
| ``` | ||
|
|
||
| Prefix search returns matching products. It does not return a dictionary of suggested terms. Before v5.4, prefix and fuzzy-prefix queries restrict `offset + limit` to 100. In v5.4, bounded pages can go beyond that window within request budgets; each page runs a new query rather than retaining a search. See [paging and prefix result windows](/reference/v5/full-text-search/querying#paging-and-prefix-result-windows). | ||
|
|
||
| REST searches every source field in the index, so the caller must be allowed to read `name`, `description`, and `tags` for these requests. Use `Table.search()` or `search_by_conditions` when a role should search only a subset of sources. See [REST full-text operators](/reference/v5/rest/querying#full-text-operators). | ||
|
|
||
| ## 5. Choose consistency behavior | ||
|
|
||
| Most searches can use the default three-second lag tolerance. When the next request must find a record already committed on the queried node, use the JavaScript API and wait for current index coverage. This wait does not wait for replication to deliver writes from another node: | ||
|
|
||
| ```javascript | ||
| for await (const product of databases.catalog.Product.search({ | ||
| conditions: [ | ||
| { | ||
| attribute: 'catalogSearch', | ||
| comparator: 'matches', | ||
| value: 'new seasonal product', | ||
| maxIndexLagMilliseconds: 0, | ||
| waitForIndexMilliseconds: 10000, | ||
| }, | ||
| ], | ||
| })) { | ||
| console.log(product); | ||
| } | ||
| ``` | ||
|
|
||
| Do not apply this wait to every catalog request without measuring it. The normal bounded-staleness path keeps search traffic independent of short indexing bursts. | ||
|
|
||
| If the index does not reach current coverage within ten seconds, iteration throws an error with `statusCode: 503`, `code: "DERIVED_INDEX_LAGGING"`, and `retryable: true`. See [freshness controls](/reference/v5/full-text-search/querying#freshness-controls). | ||
|
|
||
| ## 6. Operate the index | ||
|
|
||
| Use `describe_table` to inspect readiness. Harper reuses compatible local index files after restart and replays changes after their checkpoint. A node rebuilds locally when the files are missing, incompatible, or corrupt; ordinary table reads remain available, while full-text requests in `unknown`, `needs-rebuild`, or `rebuilding` state return `503` with `code: "INDEX_REBUILDING"` and `retryable: true`. Retry this code with backoff; do not treat every `503` as an index rebuild. Writes can also receive retryable `DERIVED_INDEX_LAGGING` responses while replay catches up after a long rebuild; see [write backpressure](/reference/v5/full-text-search/overview#write-backpressure). | ||
|
|
||
| The custom resource in step 3 streams its returned iterable. HTTP error statuses apply before response headers are sent; a failure after streaming starts is a terminal stream error and cannot change the HTTP status. Consumers must also handle that terminal error. See [freshness and streaming errors](/reference/v5/full-text-search/querying#freshness-controls). | ||
|
|
||
| See the [full-text configuration reference](/reference/v5/full-text-search/configuration) and [query reference](/reference/v5/full-text-search/querying) for every option and match mode. | ||
|
|
||
| ## Additional resources | ||
|
|
||
| - [Full-text search overview](/reference/v5/full-text-search/overview) | ||
| - [Resource API](/reference/v5/resources/resource-api) | ||
| - [Query optimization](/reference/v5/resources/query-optimization) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.