nx_search_query_parser.nx
buildroot/runtime/hub/nx_search_query_parser.nx
about
nx_search_query_parser.nx -- HUB primitive; HTTP query string -> NxSearchQuery.
COMPOSES (per NISHI_SMALL_SHARP_COMPOSABLE_STANDARD §4.1 M7):
nx_search_inverted (nx_inv_is_token_char + nx_inv_hash_bytes_lower
token-character convention reuse)
nx_hygiene_prims (NxOptPtr; safe-arith; bounded-loop budget)
COMPOSED BY:
wiki/nx_wiki_search_handler.nx (next commit; wiki wiring)
(future) obd-config-site search handler
(future) sprinkler-config-site search handler
any site that needs to parse search query strings
Status: V1 SEED. 2026-05-27. SITE-AGNOSTIC HUB PRIMITIVE per
NISHI_SMALL_SHARP_COMPOSABLE_STANDARD §4.1 M3.
WINNER-TIER: BASELINE-C provisional
INCUMBENTS: Elasticsearch's QueryParser (Lucene), Whoosh
(Python), Tantivy (Rust), Bleve (Go), Algolia's
query parser, Meilisearch's query parser
NUMBERS: V1 ships query parsing (URL-decode + tokenize +
field extraction); paired latency bench vs
Lucene QueryParser pending real query workload
GAP: Lucene QueryParser handles boolean operators
(AND/OR/NOT), phrase quotes, fuzzy ~, boost ^,
proximity ~N -- V1 ships JUST term-list parsing
(implicit AND); V2 adds the operators
PLAN: M-next: V2 boolean operators + phrase quotes;
paired latency bench vs Lucene on identical
workload
EXEMPTION REASON: n/a; provisional pending measurement
V1 SCOPE per NISHI_SEARCH_CHARTER.md §6:
- URL-decode query string (% escapes + plus-is-space)
- Extract q= terms (implicit AND; V1)
- Extract scope= (ONSITE default / OFFSITE / BOTH; sealed)
- Extract n= max results
- Tokenize per nx_search_inverted conventions
(lowercase + nx_inv_is_token_char + length 2..64)
- Sealed NxSearchQuery emit (caller-allocated buffers)
dependencies 3 imports · 6 importers
imports: nx_syscalls.nxnx_search_inverted.nxnx_hygiene_prims.nx
imported by: nx_search_handler_flow.nxnx_search_onsite_engine.nxnx_search_render_html.nxnx_search_snippet_extract.nxnx_wiki_search_render.nxnx_wiki_search_wiring.nx
structs
| 115 | struct NxSearchQuery |
consts
| 54 | const NX_SQP_OK: i64 = 0 |
| 55 | const NX_SQP_BAD_INPUT: i64 = 1500 |
| 56 | const NX_SQP_QUERY_TOO_LONG: i64 = 1501 |
| 57 | const NX_SQP_TOO_MANY_TERMS: i64 = 1502 |
| 58 | const NX_SQP_TERM_TOO_LONG: i64 = 1503 |
| 59 | const NX_SQP_TERMS_BUF_OVERFLOW: i64 = 1504 |
| 60 | const NX_SQP_BAD_URL_ESCAPE: i64 = 1505 |
| 61 | const NX_SQP_BAD_SCOPE: i64 = 1506 |
| 62 | const NX_SQP_LOOP_BUDGET: i64 = 1507 |
| 63 | const NX_SQP_NOT_IMPLEMENTED: i64 = 1508 |
| 66 | const NX_SEARCH_SCOPE_ONSITE: i64 = 0 // default; local docs only |
| 67 | const NX_SEARCH_SCOPE_OFFSITE: i64 = 1 // crawled offsite content only (V3 ships engine) |
| 68 | const NX_SEARCH_SCOPE_BOTH: i64 = 2 // federated; merged |
| 69 | const NX_SEARCH_SCOPE_N: i64 = 3 |
| 78 | const NX_SQP_MAX_QUERY_LEN: i64 = 2048 // total query-string bytes |
| 79 | const NX_SQP_MAX_TERMS: i64 = 32 // hard cap per query (per NX_INV_MAX_POSTINGS_PER hint) |
| 80 | const NX_SQP_MAX_TERM_LEN: i64 = 64 // per NX_INV_MAX_TOKEN_LEN |
| 81 | const NX_SQP_MIN_TERM_LEN: i64 = 2 // per NX_INV_MIN_TOKEN_LEN |
| 82 | const NX_SQP_TERMS_BUF_MIN_CAP: i64 = 1024 // 32 terms * 32 avg bytes |
| 83 | const NX_SQP_DEFAULT_MAX_RESULTS: i64 = 20 |
| 84 | const NX_SQP_HARD_MAX_RESULTS: i64 = 1000 |
| 85 | const NX_SQP_LOOP_BUDGET: i64 = 100000 // per-parse iteration cap |
| 88 | const NX_SQP_ASCII_PLUS: i64 = 0x2B // '+' |
| 89 | const NX_SQP_ASCII_PERCENT: i64 = 0x25 // '%' |
| 90 | const NX_SQP_ASCII_AMP: i64 = 0x26 // '&' |
| 91 | const NX_SQP_ASCII_EQ: i64 = 0x3D // '=' |
| 92 | const NX_SQP_ASCII_SP: i64 = 0x20 // ' ' |
| 95 | const NX_SQP_PARAM_Q: *u8 = "q" as *u8 |
| 96 | const NX_SQP_PARAM_Q_N: i64 = 1 |
| 97 | const NX_SQP_PARAM_SCOPE: *u8 = "scope" as *u8 |
| 98 | const NX_SQP_PARAM_SCOPE_N: i64 = 5 |
| 99 | const NX_SQP_PARAM_N: *u8 = "n" as *u8 |
| 100 | const NX_SQP_PARAM_N_N: i64 = 1 |
| 103 | const NX_SQP_SCOPE_VAL_ONSITE: *u8 = "onsite" as *u8 |
| 104 | const NX_SQP_SCOPE_VAL_ONSITE_N: i64 = 6 |
| 105 | const NX_SQP_SCOPE_VAL_OFFSITE: *u8 = "offsite" as *u8 |
| 106 | const NX_SQP_SCOPE_VAL_OFFSITE_N: i64 = 7 |
| 107 | const NX_SQP_SCOPE_VAL_BOTH: *u8 = "both" as *u8 |
| 108 | const NX_SQP_SCOPE_VAL_BOTH_N: i64 = 4 |
| 129 | const NX_SQP_TERM_KIND_SHOULD: i64 = 0 // bare term (V1 default; preserved) |
| 130 | const NX_SQP_TERM_KIND_MUST: i64 = 1 // "+term" -- doc must contain |
| 131 | const NX_SQP_TERM_KIND_MUST_NOT: i64 = 2 // "-term" -- doc must NOT contain |
| 132 | const NX_SQP_TERM_KIND_N: i64 = 3 |
functions
| 71 | func nx_search_scope_is_valid(s: i64) -> i64 |
| 135 | func nx_search_query_init(q: *NxSearchQuery, |
| 160 | func nx_sqp_hex_val(c: i64) -> i64 called by 1: nx_sqp_decode_one |
| 169 | func nx_sqp_decode_one(src: *u8, src_n: i64, idx: i64, |
| 199 | func nx_sqp_bytes_eq(a: *u8, a_n: i64, b: *u8, b_n: i64) -> i64 |
| 218 | func nx_search_query_append_term_kind(q: *NxSearchQuery, called by 2: nx_search_query_append_termnx_search_query_tokenize_value calls 1: nx_inv_is_token_char |
| 258 | func nx_search_query_append_term(q: *NxSearchQuery, calls 1: nx_search_query_append_term_kind |
| 268 | func nx_search_query_tokenize_value(q: *NxSearchQuery, |
| 342 | func nx_search_query_parse_scope_value(q: *NxSearchQuery, |
| 365 | func nx_search_query_parse_n_value(q: *NxSearchQuery, called by 1: nx_search_query_parse |
| 390 | func nx_search_query_parse(q: *NxSearchQuery, |
| 492 | func nx_search_query_count(q: *NxSearchQuery) -> i64 |
| 498 | func nx_search_query_term_at(q: *NxSearchQuery, idx: i64, |
| 525 | func nx_search_query_scope(q: *NxSearchQuery) -> i64 called by 1: nx_srh_write_banner |
| 530 | func nx_search_query_max_results(q: *NxSearchQuery) -> i64 |
| 537 | func nx_search_query_term_kind_at(q: *NxSearchQuery, idx: i64) -> i64 called by 1: nx_search_onsite_run |