Streaming expressions & Parallel SQL
|
This section documents the current Solr line (10.0; 9.10.x the maintained 9.x branch), written and verified against the Apache Solr Reference Guide. No specific patch version is pinned. Some capabilities (the Solr Operator on Kubernetes, the package-manager ecosystem, Learning To Rank model training, and expert plugin development) are linked, not documented in depth. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Every page so far reaches Solr through a single request against one collection. Streaming
expressions are the opposite: composable functions that read and combine entire result sets
across a SolrCloud collection (or several), running as a distributed dataflow instead of a top-N
query — Solr’s answer to "join two collections", "aggregate more values than a single request
can rank", or "walk a graph of relationships between documents". Parallel SQL is a SELECT
front end that compiles down to the same streaming-expression engine, so ordinary SQL clients and
JDBC drivers can drive it without learning the expression syntax directly. This page covers both.
For the request-parameter and JSON-body query surfaces these sit alongside, see
Query basics & parameters and
The JSON Request API; for the cluster topology streaming expressions fan out
across, see SolrCloud architecture.
The /stream and /sql handlers
Both handlers are implicitly defined — no solrconfig.xml entry is required to use them.
/stream takes an expr parameter holding a streaming expression and returns a JSON stream of
tuples (flat key/value maps, one per result row). /sql takes a stmt parameter holding a SQL
statement, compiles it to a streaming expression internally, and returns the same tuple stream.
curl "http://localhost:8983/solr/books/stream" \
--data-urlencode 'expr=search(books,
q="genre:fiction",
fl="title,author,price",
sort="price desc",
rows=10)'
curl "http://localhost:8983/solr/books/sql" \
--data-urlencode 'stmt=SELECT author, COUNT(*) AS book_count
FROM books
WHERE in_stock = true
GROUP BY author
ORDER BY book_count DESC
LIMIT 10' \
--data-urlencode 'aggregationMode=facet'
See Streaming Expressions and SQL Query Language for the full reference to each handler.
Streaming expression basics
A streaming expression is a function call whose arguments can themselves be streaming
expressions, nesting arbitrarily deep — the same shape as a Unix pipeline written as nested calls
instead of |. The outermost function is always a source (it produces the initial tuple
stream); decorators wrap a source (or another decorator) to transform, join, or aggregate that
stream before it reaches the client. Every field referenced anywhere in the expression must be
stored or a docValues field — streaming expressions read column data, not the inverted index
directly.
sort(
select(
search(books, q="*:*", fl="title,genre,price", sort="id asc"),
title, genre, round(price) as rounded_price
),
by="rounded_price desc"
)
Stream sources
A source is where a streaming expression’s data comes from. The three most common:
| Source | Produces |
|---|---|
|
A tuple per document matching a query against one collection — |
|
One tuple per bucket of a |
|
A tuple per document from a random sample of a query’s matches ( |
curl "http://localhost:8983/solr/books/stream" \
--data-urlencode 'expr=facet(books,
q="*:*",
buckets="genre",
bucketSorts="count(*) desc",
bucketSizeLimit=20,
sum(price), count(*))'
Other sources worth knowing by name: export and topic for full, cursor-driven or change-log
reads of a collection; jdbc to pull tuples from an external relational database into the same
pipeline; knnSearch for a vector-similarity tuple stream (see
Dense vector search). The complete list, with every parameter, is in
Streaming
Expressions under Stream Source Reference.
Stream decorators
Decorators wrap a source (or another decorator) and transform the tuple stream flowing through them:
| Decorator | Effect |
|---|---|
|
Project, rename and compute fields on each tuple — |
|
An in-memory equi-join between a larger stream and a smaller one held entirely in
memory ( |
|
Group tuples by one or more fields and compute aggregate metrics per group
( |
|
Filter tuples after a |
curl "http://localhost:8983/solr/books/stream" \
--data-urlencode 'expr=having(
rollup(
search(books, q="*:*", fl="genre,price", sort="genre asc"),
over="genre",
sum(price), count(*)
),
gt(count(*), 5)
)'
Further decorators worth naming: sort and unique to order and dedupe a stream, merge to
combine several already-sorted sources, parallel to fan an expression out across a
SolrCloud architecture worker collection (see below), and top /
reduce / group for other grouping shapes. The full set is in
Streaming
Expressions under Stream Decorator Reference.
Evaluators — math expressions
Evaluators are the functions usable inside select, having and other decorator arguments — arithmetic (add, mult, div), comparisons (gt, lt, eq), conditionals (if), string and
date functions, and a much larger math expressions library shared with Solr’s statistics and
machine-learning streaming functions (regression, correlation, distributions, matrix and vector
operations, and time-series functions such as timeseries(), moving averages, and ARIMA-style
forecasting — commonly wired into Apache Zeppelin notebooks for interactive analysis and
visualization). A having predicate and a select computed field both use this same evaluator
grammar. This page only establishes the streaming/SQL foundation those build on; see
Streaming
Expressions under Stream Evaluator Reference, Math Expressions, and Visualization for that surface
in full.
Graph traversal — nodes()
nodes() performs breadth-first graph traversal over a collection treated as a set of edges,
walking from a starting set of node values across a gather field and (optionally) a walk edge
definition, with built-in cycle detection so a traversal never revisits a node. Earlier Solr
versions exposed this as a separate gatherNodes() function; nodes() is the current, merged
form and is the one to reach for. Traversals nest to walk multiple hops, and the result can feed into any other streaming
expression — scoreNodes for recommendation-style ranking, or export of the gathered node set as
GraphML for visualization.
curl "http://localhost:8983/solr/emails/stream" \
--data-urlencode 'expr=nodes(emails,
walk="johndoe@example.com->from",
gather="to",
count(*))'
See Graph Traversal for the full parameter set, filtering, and multi-collection traversal.
Parallel SQL over JDBC
SELECT statements against /sql compile through Apache Calcite into a streaming expression — each collection queried behaves as though it were a SQL table, with indexed/docValues fields as
its columns. The supported surface is SELECT (with DISTINCT), WHERE, GROUP BY, HAVING,
ORDER BY, LIMIT, and the same statistical/aggregate functions the streaming-expression layer
exposes — there is no INSERT/UPDATE/DELETE, and no arbitrary multi-way JOIN beyond what a
hashJoin-backed plan can express. Contrast the full language on
SQL Queries.
SELECT genre, COUNT(*) AS book_count, AVG(price) AS avg_price
FROM books
WHERE in_stock = true
GROUP BY genre
HAVING COUNT(*) > 5
ORDER BY book_count DESC
LIMIT 20
Connect with the bundled JDBC driver using a ZooKeeper-based connection string:
jdbc:solr://<zkHost>?collection=books&aggregationMode=facet&numWorkers=2
Aggregation modes — facet vs. map_reduce
aggregationMode picks how GROUP BY is executed: facet (the default) pushes the aggregation
down into Solr’s own faceting engine per shard — fast, but best suited to low-to-moderate
cardinality grouping fields, the same trade-off facet.method makes for classic faceting (see
Faceting). map_reduce instead shuffles tuples across a worker
collection so they land on the same worker by grouping-key hash, then aggregates locally — higher
network cost, but no cardinality ceiling.
Worker collections and numWorkers
map_reduce aggregation, explicit parallel() streaming expressions, and large hashJoin`s all
need a worker collection: an ordinary SolrCloud collection (it holds no data of its own for this
purpose) whose shards act as the compute tier tuples get partitioned across. `numWorkers on the
JDBC connection string (or the workers parameter on parallel()) sets how many of that
collection’s shards participate. Undersizing it caps parallelism; oversizing it beyond the
available shards wastes nothing but is not necessary either. See
Collections API, configsets & replica placement for creating a collection, and
SolrCloud architecture for how shards and replicas map to this compute
role.
Further reading
For the cluster this fans out across, see
SolrCloud architecture and
Collections API, configsets & replica placement; for the aggregation surface facet sources
and rollup overlap with, see Faceting.