Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions content/develop/ai/search-and-query/indexing/interactive-demo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
categories:
- docs
- develop
- stack
- oss
- rs
- rc
- oss
- kubernetes
- clients
description: Change an index's field types, prefixes, and options, and see what each query finds, with every redis-cli command and reply.
linkTitle: Interactive demo
title: Indexing interactive demo
weight: 4
---

Redis Search only finds data that's in an index. You create an index with `FT.CREATE`, which says which keys to include and how to treat each of their fields, and Redis keeps it up to date as the data changes. How you define the index decides what queries can find.

This demo starts with the data itself, the restaurants of a food delivery app, then creates an index on it and lets you change it: the field types, the keys it covers, how it handles missing and invalid values, and whether the restaurants are hashes or JSON documents. For each change, it shows the commands, the stored data, and which restaurants the query finds.

The demo runs in your browser and doesn't connect to Redis. Its commands and replies were recorded from Redis, each starting from an empty database, and they work as shown in `redis-cli`.

{{< search-index-demo >}}

## What the demo shows

1. **The data.** Each restaurant is a hash: a key whose name starts with `restaurant:`, holding named fields. Every value in a hash is a string, and Redis finds a hash by its key, not by its contents, until you index it.
2. **Field types.** `TEXT` fields match words, including other forms of each word, such as plurals. `TAG` fields match whole values. `NUMERIC` fields match ranges. A query that doesn't fit the field's type finds nothing, and with `DIALECT 2` it returns an error instead. See [Field and type options](/content/develop/ai/search-and-query/indexing/field-and-type-options.md).
3. **Which keys.** `PREFIX` decides which keys an index covers, and `FILTER` narrows them down by value. Redis indexes each later write as it happens. See [`FT.CREATE`](/content/commands/ft.create.md).
4. **Missing and bad values.** A value that doesn't fit its field's type keeps Redis from indexing the whole hash, and [`FT.INFO`](/content/commands/ft.info.md) counts the failure. `INDEXMISSING` and `INDEXEMPTY` let you query for missing and empty values. See [Index missing or empty values](/content/develop/ai/search-and-query/indexing/_index.md#index-missing-or-empty-values).
5. **Hash or JSON.** An index on JSON documents names JSON paths and gives each one a name with `AS`. Queries are the same as for hashes, but a JSON index returns whole documents as JSON, and Redis 8 doesn't support `HIGHLIGHT` on it. See [Indexing](/content/develop/ai/search-and-query/indexing/_index.md).
34 changes: 34 additions & 0 deletions layouts/shortcodes/search-index-demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{{- /*
search-index-demo
-----------------
Scripted, dependency-free demo of Redis Search indexing: field types,
which keys an index covers, missing and invalid values, and hashes
compared with JSON, each with its commands and the data they find.
Runs entirely in the browser on recorded data; no Redis server is called.

Usage (no arguments):
{{< search-index-demo >}}

Base styles come from context-retriever-demo.css and search-demo.css so
the scripted demos share one look; search-index-demo.css adds this
demo's pieces.

The assets are emitted on every call rather than deduplicated with
.Page.Store: the store survives `hugo server` rebuilds, so a page that
isn't edited would lose its assets after the first rebuild. The script
guards against running twice if the shortcode is used more than once.
*/ -}}

{{- /* ?v= is a hash of the file, so a reload never runs a stale cached copy. */ -}}
{{- $v := dict -}}
{{- range slice "css/context-retriever-demo.css" "css/search-demo.css" "css/search-index-demo.css" "js/search-index-demo.js" -}}
{{- $v = merge $v (dict . (substr (md5 (os.ReadFile (print "static/" .))) 0 10)) -}}
{{- end }}
<link rel="stylesheet" href="{{ "css/context-retriever-demo.css" | relURL }}?v={{ index $v "css/context-retriever-demo.css" }}">
<link rel="stylesheet" href="{{ "css/search-demo.css" | relURL }}?v={{ index $v "css/search-demo.css" }}">
<link rel="stylesheet" href="{{ "css/search-index-demo.css" | relURL }}?v={{ index $v "css/search-index-demo.css" }}">
<script defer src="{{ "js/search-index-demo.js" | relURL }}?v={{ index $v "js/search-index-demo.js" }}"></script>

<div class="rcr rsq rsi" data-search-index>
<noscript><p>This interactive demo needs JavaScript.</p></noscript>
</div>
50 changes: 50 additions & 0 deletions static/css/search-demo.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* =========================================================================
Redis Search scripted demo: querying
Additions to context-retriever-demo.css, which the shortcode loads first
so the scripted demos share one look. Scoped under `.rsq`; the root
element also carries `.rcr` for the shared tokens and base styles.
========================================================================= */

/* ---- the controls ---- */
.rsq-controls { display: grid; gap: 10px; margin-bottom: 12px; }
.rsq-choice { display: grid; gap: 5px; min-width: 0; }
.rsq-opts { display: flex; flex-wrap: wrap; gap: 6px; }
.rsq .rsq-opt { padding: 4px 11px; font-size: 13px; }
.rsq .rsq-opt.is-on { border-color: var(--rcr-accent-2); color: var(--rcr-accent-2); background: rgba(89, 97, 255, .07); font-weight: 600; }
.rsq .rsq-opt code { background: none; padding: 0; font-size: 12px; color: inherit; }
.rsq-filters { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; padding: 10px 12px; border: 1px solid var(--rcr-line); border-radius: 10px; background: var(--rcr-surface-2); }
@container (min-width: 560px) { .rsq-filters { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }

/* ---- the results ---- */
.rsq-results { margin-bottom: 12px; }
.rsq-list { display: grid; grid-template-columns: minmax(0, 1fr); gap: 4px; }
.rsq-axis { font-size: 11.5px; color: var(--rcr-faint); margin-bottom: 2px; }
.rsq-empty { font-size: 13px; color: var(--rcr-faint); }
.rsq-row { display: grid; gap: 1px; padding: 7px 10px; border: 1px solid rgba(255, 68, 56, .25); border-radius: 8px; background: rgba(255, 68, 56, .06); font-size: 13.5px; cursor: pointer; }
.rsq-row:hover { border-color: rgba(255, 68, 56, .45); }
.rsq-row:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--rcr-focus); }
.rsq-row.is-open { border-color: var(--rcr-accent-2); }
.rsq-meta { font-size: 11.5px; color: var(--rcr-muted); margin-left: 8px; }
.rsq-desc { font-size: 13px; color: var(--rcr-muted); }
.rsq mark.rsq-hit { background: #fde0c2; color: var(--rcr-ink); border-radius: 3px; padding: 0 2px; box-shadow: inset 0 -2px 0 #b35900; }
.rsq-pager { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 8px; }
.rsq-pager .rcr-faint { margin-right: 4px; }
.rsq-group { display: grid; grid-template-columns: minmax(0, 1fr) minmax(60px, 140px) 4.6em; align-items: center; gap: 10px; padding: 6px 9px; border-radius: 8px; background: var(--rcr-surface-2); }
.rsq-gname { display: grid; min-width: 0; font-size: 13.5px; }
.rsq-gname .rsq-meta { margin-left: 0; }
.rsq-bar { position: relative; height: 8px; border-radius: 4px; background: var(--rcr-line); }
.rsq-bar > span { position: absolute; inset: 0 auto 0 0; border-radius: 4px; background: var(--rcr-accent); }
.rsq-val { text-align: right; font-size: 12.5px; }
.rsq-note { margin-top: 10px; padding: 9px 12px; border-left: 3px solid var(--rcr-accent-2); border-radius: 0 8px 8px 0; background: rgba(89, 97, 255, .06); font-size: 13.5px; }

/* ---- what redis-cli shows: the command, the reply, the index, and the documents ---- */
.rsq-codehead { flex-wrap: wrap; }
.rsq-codehead .rcr-subtabs { margin-bottom: 0; border-bottom: 0; }
.rsq pre.rsq-pre { font-size: 12px; line-height: 1.5; white-space: pre; overflow-x: auto; margin-bottom: 8px; max-height: 420px; }
.rsq-prompt { color: var(--rcr-faint); user-select: none; }
.rsq-doc { min-width: 0; margin: 2px 0 8px; }
.rsq .rsq-doc pre.rsq-pre { white-space: pre-wrap; overflow-wrap: anywhere; max-height: none; }
.rsq-keys { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 8px; }
.rsq .rsq-keys .rsq-opt { font-size: 12px; }
/* The command is one line for redis-cli; it wraps here so all of it is in view. */
.rsq pre.rsq-pre.rsq-cmdline { white-space: pre-wrap; overflow-wrap: anywhere; }
105 changes: 105 additions & 0 deletions static/css/search-index-demo.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/* =========================================================================
Redis Search scripted demo: indexing
Additions to context-retriever-demo.css and search-demo.css, which the
shortcode loads first so the scripted demos share one look. Scoped under
`.rsi`; the root element also carries `.rcr` and `.rsq` for the shared
tokens and pieces.
========================================================================= */

/* ---- errors, gaps in the data, and hashes left out ---- */
.rsi .rsi-count-err { color: var(--rcr-bad); }
.rsi-gap { color: var(--rcr-bad); font-weight: 600; }

/* Labels are upper case; the literals inside them, such as a field name or a key prefix, keep their case. */
.rsi .rcr-lbl code { text-transform: none; }
.rsi ul.rsi-list { margin: 6px 0 0; padding-left: 1.2em; list-style: disc; }
.rsi ul.rsi-list li { margin: 2px 0; padding: 0; }
.rsi-hint { font-size: 12px; color: var(--rcr-faint); }

/* ---- the data: one row per document, the headers showing how the index treats each field ---- */
.rsi-data { margin-bottom: 12px; }
.rsi .rsi-table { width: 100%; table-layout: fixed; border-collapse: separate; border-spacing: 0 3px; margin: 2px 0 6px; font-size: 13px; line-height: 1.4; }
.rsi .rsi-table th { padding: 2px 8px 4px; border: 0; background: none; text-align: left; vertical-align: bottom; color: var(--rcr-muted); font-size: 11.5px; font-weight: 600; }
.rsi .rsi-table th:first-child { width: 31%; }
.rsi .rsi-table.has-3 th:first-child { width: 27%; }
.rsi .rsi-table.has-3 th:nth-child(2) { width: 31%; }
.rsi .rsi-table th .rcr-mono { font-size: 11.5px; }
.rsi .rsi-table td { padding: 6px 8px; border: 0; border-top: 1px solid var(--rcr-line); border-bottom: 1px solid var(--rcr-line); background: var(--rcr-surface-2); vertical-align: top; overflow-wrap: break-word; }
.rsi .rsi-table td:first-child { border-left: 1px solid var(--rcr-line); border-radius: 8px 0 0 8px; }
.rsi .rsi-table td:last-child { border-right: 1px solid var(--rcr-line); border-radius: 0 8px 8px 0; }
.rsi .rsi-table td[colspan] { border-radius: 8px; }
.rsi .rsi-table tr[data-key] { cursor: pointer; }
.rsi .rsi-table tr[data-key]:hover td { border-color: var(--rcr-line-strong); }
.rsi .rsi-table tr.is-hit td { background: rgba(255, 68, 56, .07); border-color: rgba(255, 68, 56, .35); }
.rsi .rsi-table tr.is-hit:hover td { border-color: rgba(255, 68, 56, .55); }
.rsi .rsi-table tr.is-open td { border-color: var(--rcr-accent-2); }
.rsi .rsi-table tr.is-out td { color: var(--rcr-faint); }
.rsi .rsi-table tr.rsi-docrow td, .rsi .rsi-table tr.rsi-docrow td:first-child { padding: 0; border: 0; background: none; }
.rsi .rsi-table tr.rsi-docrow .rsq-doc { margin: 0 0 4px; }
.rsi .rsi-table code { font-size: 11.5px; }
.rsi .rsi-open { display: inline; padding: 0; border: 0; background: none; color: inherit; font-size: 11.5px; text-align: left; cursor: pointer; overflow-wrap: anywhere; }
.rsi .rsi-open:focus-visible { outline: none; border-radius: 3px; box-shadow: 0 0 0 3px var(--rcr-focus); }
.rsi .rsi-type { display: inline-block; padding: 0 5px; border-radius: 4px; background: var(--rcr-line); color: var(--rcr-ink); font-size: 10px; font-weight: 700; letter-spacing: .02em; }
.rsi .rsi-type.is-marked { background: rgba(89, 97, 255, .14); color: var(--rcr-accent-2); }
.rsi-back { display: block; margin-top: 3px; font-size: 12px; color: var(--rcr-accent-2); }
.rsi-tag { display: inline-block; margin-left: 5px; color: var(--rcr-accent-2); font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; text-decoration: none; }
.rsi tr.is-out .rsi-tag { color: var(--rcr-muted); }

.rsi .rsi-table tr[hidden] { display: none; }
/* On narrow screens each row becomes a card: the key on top, the fields under their headers. */
@container (max-width: 440px) {
.rsi .rsi-table, .rsi .rsi-table thead, .rsi .rsi-table tbody { display: block; }
.rsi .rsi-table tr { display: grid; grid-template-columns: var(--cols); column-gap: 12px; margin-bottom: 4px; padding: 6px 9px; font-size: 12.5px;
border: 1px solid var(--rcr-line); border-radius: 8px; background: var(--rcr-surface-2); }
.rsi .rsi-table thead tr { margin-bottom: 2px; padding: 0 9px; border: 0; background: none; }
.rsi .rsi-table th, .rsi .rsi-table td, .rsi .rsi-table td:first-child, .rsi .rsi-table td:last-child { padding: 0; border: 0; border-radius: 0; background: none; }
.rsi .rsi-table th:first-child { display: none; }
.rsi .rsi-table td.rsi-key, .rsi .rsi-table td[colspan], .rsi .rsi-table .rsi-wide { grid-column: 1 / -1; }
.rsi .rsi-table td.rsi-key { margin-bottom: 2px; }
.rsi .rsi-table tr[data-key]:hover { border-color: var(--rcr-line-strong); }
.rsi .rsi-table tr.is-hit { border-color: rgba(255, 68, 56, .35); background: rgba(255, 68, 56, .07); }
.rsi .rsi-table tr.is-hit td { background: none; }
.rsi .rsi-table tr.is-open { border-color: var(--rcr-accent-2); }
.rsi .rsi-table tr.rsi-docrow { padding: 0; border: 0; background: none; }
.rsi .rsi-table tr[hidden] { display: none; }
}


/* ---- step 4: what FT.INFO reports ---- */
.rsi-info { display: grid; gap: 8px; margin: 4px 0; padding: 10px 12px; border: 1px solid var(--rcr-line); border-radius: 8px; }
.rsi-infohead { font-size: 12.5px; font-weight: 600; }
.rsi-inforow { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1px; }
@container (min-width: 620px) { .rsi-inforow { grid-template-columns: 13.5em minmax(0, 1fr); gap: 14px; } }
.rsi .rsi-k { color: var(--rcr-muted); font-size: 12px; }
.rsi .rsi-v { overflow-wrap: anywhere; }

/* ---- the step's FT.CREATE, part by part, each part with what it means ---- */
.rsi-create { margin: 0 0 12px; padding: 10px 12px; border: 1px solid var(--rcr-line); border-radius: 10px; background: var(--rcr-code-bg); }
.rsi-createhead { margin-bottom: 6px; font-size: 12.5px; font-weight: 600; }
.rsi-cline { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: 12px; padding: 1px 0; font-size: 12.5px; line-height: 1.45; }
.rsi-ccode { font-family: var(--rcr-mono); white-space: pre-wrap; overflow-wrap: anywhere; }
.rsi-ccode.rsi-d1 { padding-left: 2ch; }
.rsi-ccode.rsi-d2 { padding-left: 4ch; }
.rsi-ccode, .rsi-cnote { --ch: 7.5px; } /* one monospace character at 12.5px, so the notes indent with the code above them */
.rsi-cnote { color: var(--rcr-muted); }
.rsi .rsi-cnote code { font-size: 11.5px; background: rgba(9, 26, 35, .06); }
.rsi-createcap { margin-top: 6px; font-size: 12px; }
.rsi .rsi-copy { padding: 0; border: 0; background: none; color: var(--rcr-accent-2); font-size: 12px; font-weight: 600; cursor: pointer; }
.rsi .rsi-copy:focus-visible { outline: none; border-radius: 3px; box-shadow: 0 0 0 3px var(--rcr-focus); }
@container (max-width: 440px) {
.rsi-cline { grid-template-columns: minmax(0, 1fr); gap: 0; margin-bottom: 4px; }
.rsi-cnote { padding-left: calc(var(--ch) * 2); font-size: 12px; }
.rsi-cnote.rsi-d1 { padding-left: calc(var(--ch) * 4); }
.rsi-cnote.rsi-d2 { padding-left: calc(var(--ch) * 6); }
}

/* the query, as typed, then what it asks for */
.rsi-output { margin-top: 12px; }
.rsi .rsi-output pre.rsq-pre { margin: 0; max-height: 320px; background: var(--rcr-surface); }
.rsi .rsi-output pre.rsq-pre.is-err { color: var(--rcr-bad); }
.rsi-qline { font-family: var(--rcr-mono); font-size: 12.5px; white-space: pre-wrap; overflow-wrap: anywhere; }
.rsi-qnote { margin-top: 4px; font-size: 12.5px; color: var(--rcr-muted); }
.rsi .rsi-qnote code { font-size: 11.5px; background: rgba(9, 26, 35, .06); }

/* ---- the parts of a command the controls change ---- */
.rsi mark.rsi-mark { background: rgba(89, 97, 255, .14); color: inherit; border-radius: 3px; padding: 0 1px; }
Loading
Loading