Skip to content

Standing Queries Quickstart

A standing query is a feature unique to Quine. Standing queries monitor streams for specified patterns, maintaining partial matches, and executing user-specified actions the instant a full match is made. Actions can include anything from updating the graph itself by creating new nodes or edges, writing results out to Kafka or Kinesis, or posting results to a webhook. Whatever fits your streaming data pipeline.

Standing queries are a unique (and we think powerful) feature of Quine. Standing queries are key to how Quine can achieve sub-millisecond performance finding complex patterns (or subgraphs) in high volume event streams. Standing queries are also a key reason why Quine does not impose time windows, setting it apart from other event processing systems.

Format of a standing query

A standing query is defined in two parts: a pattern and an output. The pattern defines what we want to match, expressed in Cypher using the form MATCH … WHERE … RETURN …. The output defines the action(s) to take for each result produced by the RETURN in the pattern query.

In general, a standing query definition JSON object takes form as in this example.

{
  "name": "kids-with-dads",
  "pattern": {
    "query": "MATCH (n)-[:has_father]->(m) WHERE n.name IS NOT NULL AND m.name IS NOT NULL RETURN DISTINCT strId(n) AS kidWithDad",
    "type": "Cypher"
  },
  "outputs": [
    {
      "name": "file-of-results",
      "destinations": [
        { "type": "File", "path": "kidsWithDads.jsonl" }
      ]
    }
  ]
}

A StandingQueryResult is an object with 2 sub-objects: meta and data. The meta object contains a boolean isPositiveMatch.

When the pattern query makes a positive match, the data object consists of the data returned by the Standing Query.

For example, a StandingQueryResult may look like the following:

{
    "meta": {
        "isPositiveMatch": true
    },
    "data": {
        "kidWithDad": "a0f93a88-ecc8-4bd5-b9ba-faa6e9c5f95d"
    }
}

There are many standing query destination types. The API documentation defines the format for each destination type. The types are:

Name Configuration value Formats Description
Broadcast to Reactive Stream ReactiveStream JSON, Protobuf Broadcasts data to a created Reactive Stream. Other thatDot products can subscribe to Reactive Streams. Reactive Stream outputs are only supported in standalone (single-host) deployments.
Drop Drop — Effectively no destination at all, this does nothing but forget the data sent to it.
Log JSON to Console StandardOut JSON Prints each result as a single-line JSON object to stdout on the application server.
POST to HTTP[S] Webhook HttpEndpoint JSON Makes an HTTP[S] POST for each result. For the format of the result, see "Standing Query Result Output".
Publish to Kafka Topic Kafka JSON, Protobuf Publishes provided data to the specified Apache Kafka topic.
Publish to Kinesis Data Stream Kinesis JSON, Protobuf Publishes provided data to the specified Amazon Kinesis stream.
Publish to Slack Webhook Slack Slack message Sends a message to Slack via a configured webhook URL. See https://api.slack.com/messaging/webhooks.
Publish to SNS Topic SNS JSON, Protobuf Publishes an AWS SNS record to the provided topic. To guarantee delivery, writes that fail are retried indefinitely, so confirm the credentials and topic ARN before starting this output. An unfixable error (e.g., an invalid topic ARN or missing credentials) will retry forever without emitting results, which may stop the Standing Query this output is attached to.
Run Cypher Query CypherQuery — Runs the query, where the given parameter is used to reference the data that is passed in. Runs at most parallelism queries simultaneously.
Write JSON to File File JSON Writes each result as a single-line JSON record. For the format of the result, see "Standing Query Result Output".

Each output workflow can optionally include a resultEnrichment step to enrich data with a Cypher query before sending to destinations.

Writing a standing query

Remember the scenario that we developed in the ingest stream tutorial:

For the sake of this tutorial, assume that you need to separate human-generated events from bot-generated events in the English Wikipedia database and send them to a destination in your data pipeline for additional processing.

Our ingest stream is processing events as they arrive from the mediawiki.revision-create event source and manifesting them as nodes in the graph.

Our task is to find the human-generated events (not bots) in the stream, separate them from the rest of the stream and send them to another service for processing.

  1. Write a Cypher query to match our scenario.

Using the Exploration UI we can develop a Cypher query that returns 10 matching revision-create event nodes.

MATCH (userNode:user {user_is_bot: false})-[:RESPONSIBLE_FOR]->(revNode:revision {database: 'enwiki'})
RETURN DISTINCT strid(userNode) as NodeID,
  revNode.page_title as Title,
  revNode.performer.user_text as User
LIMIT 10

Quine Exploration UI

We can use this Cypher query to develop the standing query.

  1. Write the pattern portion of the standing query
MATCH (userNode:user {user_is_bot: false})-[:RESPONSIBLE_FOR]->(revNode:revision {database: 'enwiki'})
RETURN DISTINCT id(revNode) as id
  1. Write the output portion of the standing query
MATCH (n)
WHERE id(n) = $that.data.id
RETURN properties(n)
  1. Form the standing query JSON object
{
  "name": "not-a-bot",
  "pattern": {
    "query": "MATCH (userNode:user {user_is_bot: false})-[:RESPONSIBLE_FOR]->(revNode:revision {database: 'enwiki'}) RETURN DISTINCT id(revNode) as id",
    "type": "Cypher"
  },
  "outputs": [
    {
      "name": "print-output",
      "resultEnrichment": {
        "query": "MATCH (n) WHERE id(n) = $that.data.id RETURN properties(n)",
        "parameter": "that"
      },
      "destinations": [
        { "type": "StandardOut" }
      ]
    }
  ]
}

For simplicity, I am sending the output from the standing query to the console. You could easily send the output to another server via a webhook.

  1. POST the standing query via curl to Create Standing Query: POST /api/v2/graph/quine/standingQueries into Quine for processing.
curl -X "POST" "http://127.0.0.1:8080/api/v2/graph/quine/standingQueries" \
     -H 'Content-Type: application/json' \
     -d $'{
  "name": "not-a-bot",
  "pattern": {
    "query": "MATCH (userNode:user {user_is_bot: false})-[:RESPONSIBLE_FOR]->(revNode:revision {database: \'enwiki\'}) RETURN DISTINCT id(revNode) as id",
    "type": "Cypher"
  },
  "outputs": [
    {
      "name": "print-output",
      "resultEnrichment": {
        "query": "MATCH (n) WHERE id(n) = $that.data.id RETURN properties(n)",
        "parameter": "that"
      },
      "destinations": [
        { "type": "StandardOut" }
      ]
    }
  ]
}'

Almost immediately you should see "non-bot" generated events appear in the terminal window similar to the one below. Because this output has a resultEnrichment query, each line holds the columns that query returns instead of the meta and data wrapper.

{"properties(n)":{"$schema":"/mediawiki/revision/create/2.0.0","bot":false,"comment":"Assessment (Low): banner shell, Schools ([[WP:RATER#2.7.2|Rater]])","database":"enwiki","dt":"2026-11-02T23:31:44Z","meta":{"domain":"en.wikipedia.org","dt":"2026-11-02T23:31:46.127Z","id":"6d2feee0-a39e-4646-9467-5dde5c123314","offset":1218224180,"partition":0,"request_id":"2335026a-762c-46bc-88a7-60472bcf04d6","stream":"mediawiki.revision-create","topic":"codfw.mediawiki.revision-create","uri":"https://en.wikipedia.org/wiki/Talk:South_Range_Local_School_District"},"page_id":14006495,"page_is_redirect":false,"page_namespace":1,"page_title":"Talk:South_Range_Local_School_District","parsedcomment":"Assessment (Low): banner shell, Schools (<a href=\"/wiki/Wikipedia:RATER#2.7.2\" class=\"mw-redirect\" title=\"Wikipedia:RATER\">Rater</a>)","performer":{"user_edit_count":27884,"user_groups":["extendedconfirmed","*","user","autoconfirmed"],"user_id":41527672,"user_is_bot":false,"user_registration_dt":"2021-03-29T16:10:27Z","user_text":"ChillyBlanket"},"rev_content_changed":true,"rev_content_format":"text/x-wiki","rev_content_model":"wikitext","rev_id":1383487770,"rev_len":672,"rev_minor_edit":false,"rev_parent_id":1321340929,"rev_sha1":"5qnajetkirdzocrdaqk03oyt9xxl8ba","rev_slots":{"main":{"rev_slot_content_model":"wikitext","rev_slot_origin_rev_id":1383487770,"rev_slot_sha1":"5qnajetkirdzocrdaqk03oyt9xxl8ba","rev_slot_size":672}},"rev_timestamp":"2026-11-02T23:31:44Z"}}

Next Steps

Congratulations! You have a stream of non-bot generated revision-create events isolated from the main event stream and you are displaying them in your terminal.

Up to this point, we've been using the REST API to interact with Quine. In the next section, we will collect all of the API calls that we've made and build a recipe.