Skip to content

feat(explore): add PPL and SQL as search modes - #3888

Open
stsawyer1 wants to merge 4 commits into
google:masterfrom
stsawyer1:directquery
Open

feat(explore): add PPL and SQL as search modes#3888
stsawyer1 wants to merge 4 commits into
google:masterfrom
stsawyer1:directquery

Conversation

@stsawyer1

@stsawyer1 stsawyer1 commented Jul 30, 2026

Copy link
Copy Markdown

closes #3887

What existing problem does this PR solve?

Lucene (query search/wildcard) returns the list of events which contain the search query. But, it does not allow analysts to perform extraction, transformation, or aggregation as part of the investigation process. An analyst who needs that today leaves Timesketch and queries OpenSearch directly, losing sketch scoping, the ACL check, and the timeline filter. OpenSearch already ships a plugin that can answer those questions; this exposes it to a sketch.

What new feature is being introduced?

PPL and SQL as two additional search modes with dedicated API routes, mirroring how explore_wildcard is separated from explore, so the language is fixed by the route and can never be steered by the request body:

/sketches/<id>/explore/ppl/          /sketches/<id>/explore/sql/
/sketches/<id>/explore/ppl/explain/  /sketches/<id>/explore/sql/explain/
/sketches/<id>/explore/ppl/export/   /sketches/<id>/explore/sql/export/

On query execution, the sketch's own indices are injected as its source, and a query naming an index outside the sketch is refused rather than rewritten, so a rewrite bug can't silently widen a read.

In the frontend, PPL and SQL have been added to the search mode selector. Selecting these modes swaps the Lucene input for a monospace editor and returns a table instead of an event list. Queries are linted in the browser first, catching the query that succeeds while quietly meaning something else, but never blocking a run. In the results, a cell can be pivoted into the events behind it, handing the language back to Lucene and carrying the time range across.

Requires OpenSearch 3.7.0

Scoping injects a filter ahead of the rest of the query, and the behavior that depends on settled in 3.7.0, when Calcite became the default PPL engine. Older clusters accept the same queries but differ in how a filter ahead of a stats stage is pushed down. Rather than scope correctly on some versions and subtly not on others, the capability probe refuses below 3.7.0 and the UI drops the modes—the same way it drops wildcard on a sketch without wildcard mappings. Sketch metadata reports supports_direct_query alongside supports_wildcard, cached for five minutes, so an upgraded cluster lights the feature up without a restart.

docker/dev/docker-compose.yml already runs 3.7.0. docker/release/config.env and the e2e harness pin 2.19.5, so a stock release deployment won't see the feature until the operator upgrades.

Overview of changes to existing functions

Three are trivial: route registration, the supports_direct_query metadata field, and a call in the app factory that builds the shared client at startup.

The substantive one is timesketch/lib/datastores/opensearch.py. The connection setup that OpenSearchDataStore.__init__ builds inline moves into a new build_opensearch_client() so the direct-query package derives the cluster from the same configuration rather than parsing OPENSEARCH_HOSTS twice. That body is existing code moved, not rewritten. Two things are new: the builder sends nothing to the cluster, so a client can be built at startup without a cluster that is still coming up blocking the app; and it sizes the urllib3 pool to what one process can have in flight (OPENSEARCH_POOL_MAXSIZE, default 20), where the opensearch-py default of one keep-alive connection per node makes any two overlapping requests open and discard a throwaway. That second one helps all OpenSearch traffic.

If you'd rather not touch the datastore, the package can build its own client at the cost of duplicating the config parsing, and the pooling fix can be split out.

Why I think this can go in the main branch

  • No new dependencies and no new HTTP plumbing—cluster access goes through opensearch-py's existing plugins.ppl and plugins.sql namespaces, so queries fail over between nodes like every other OpenSearch call. These plugins have identical methods and parameters in the pinned 2.8.0 version of opensearch-py through current (3.2.0).
  • Self-contained: one backend package, three Vue components, one lint utility, plus the four files above.
  • Fails safe: if OpenSearch version is too low or without the plugin, the UI never offers the modes
  • Robust test suite, weighted toward scoping and read-only enforcement.

Checks

  • All tests succeed.
  • Unit tests added.
  • e2e tests added. The e2e dockerfile defaults to 2.19.5, so this needs OPENSEARCH_VERSION raised for that job.
  • Documentation updated. Added a section to docs/guides/user/search-query-guide.md

Lucene answers "which events match", but not "how many per host" or
"which accounts appear in both timelines". OpenSearch already ships the
SQL plugin that can answer those; this exposes it to a sketch.

Each language gets its own routes under /explore/ppl/ and /explore/sql/,
mirroring how wildcard search is separated from query string search, so
the language is fixed by the route and can never be steered by the
request body. A dialect object owns what differs between the two
(read-only validation, sketch scoping, payload shape, export paging) and
one shared resource shell owns what does not.

Scoping is the security boundary: every query has the sketch's own
indices injected as its source, and any query naming an index outside
the sketch is refused rather than rewritten. Read-only enforcement runs
before that, so DELETE and DROP never reach the cluster.

The languages come from the SQL plugin rather than the search API, so
availability is a property of the cluster. The sketch metadata now
reports supports_direct_query alongside supports_wildcard, letting a
client drop them from its menu instead of offering a query that will
fail.

Queries reach the cluster through an opensearch-py client, so they fail
over between nodes like every other OpenSearch call in Timesketch. The
connection setup that OpenSearchDataStore builds inline moves into
build_opensearch_client() so both callers derive the cluster the same
way. It sends nothing to the cluster, which lets the client be built at
startup without a cluster that is still coming up blocking the app. It
also asks urllib3 for a connection pool sized to what a process can have
in flight, since the default is a single connection per node.
PPL and SQL join Query String and Wildcard in the existing search-mode
selector rather than arriving as a separate control, so there is one
place to choose how a search is written. They appear only when the
sketch metadata reports the cluster can serve them.

A direct query is a multi-line pipeline or statement, so it gets a
monospace auto-growing editor instead of the one-line Lucene input, and
returns a table rather than an event list. The Lucene-only chrome
(filter chips, the time filter menu, saved searches) is hidden while one
is selected, because a direct query carries only its query string and
the enabled timeline ids; leaving those on screen would imply a
narrowing that never reaches OpenSearch. The time range therefore moves
into the editor itself.

Aggregates are a starting point, not an answer, so a cell can be pivoted
into the events behind it: the pivot hands the language back to Lucene,
raises the chip, and carries the editor's time range over as a
datetime_range chip so the investigation does not widen back out.

Queries are linted in the browser before they are sent. The rules catch
the failure that costs the most time, a query that succeeds while
quietly meaning something else, and never block a run, since OpenSearch
is the authority on its own syntax.
@google-cla

google-cla Bot commented Jul 30, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@stsawyer1 stsawyer1 changed the title feat(explore): add PPL and SQL as search modes 🤖🤖🤖 feat(explore): add PPL and SQL as search modes Jul 31, 2026
@jaegeral

jaegeral commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Thx for the Pull request, this is a quite a lot of code, we need to discuss this internally how to approach it and ensure maintenance from our side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support PPL and SQL queries within Timesketch

2 participants