ElasticGraph::QueryInterceptor

An ElasticGraph extension for intercepting datastore queries.

Interceptors can customize/modify datastore queries before they are submitted to Elasticsearch or OpenSearch. Some of the use cases for query interceptors include:

  • Automatic filtering: A query interceptor can apply default filtering to a query. For example, this can be used to automatically exclude soft-deleted/tombstoned records.
  • Query optimization: When querying an index that uses shard routing or rollover, queries that filter on the shard routing or rollover timestamp fields are much more performant. A query interceptor can apply filters on these fields after querying a derived index for the appropriate values.

Dependency Diagram

graph LR; classDef targetGemStyle fill:#FADBD8,stroke:#EC7063,color:#000,stroke-width:2px; classDef otherEgGemStyle fill:#A9DFBF,stroke:#2ECC71,color:#000; classDef externalGemStyle fill:#E0EFFF,stroke:#70A1D7,color:#2980B9; elasticgraph-query_interceptor["elasticgraph-query_interceptor"]; class elasticgraph-query_interceptor targetGemStyle; elasticgraph-graphql["elasticgraph-graphql"]; elasticgraph-query_interceptor --> elasticgraph-graphql; class elasticgraph-graphql otherEgGemStyle; elasticgraph-schema_artifacts["elasticgraph-schema_artifacts"]; elasticgraph-query_interceptor --> elasticgraph-schema_artifacts; class elasticgraph-schema_artifacts otherEgGemStyle;

Setup

First, add elasticgraph-query_interceptor to your Gemfile:

diff --git a/Gemfile b/Gemfile
index 4a5ef1e..5c16c2b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -8,6 +8,7 @@ gem "elasticgraph-query_registry", *elasticgraph_details

 # Can be elasticgraph-elasticsearch or elasticgraph-opensearch based on the datastore you want to use.
 gem "elasticgraph-opensearch", *elasticgraph_details
+gem "elasticgraph-query_interceptor", *elasticgraph_details

 gem "httpx", "~> 1.3"

Next, define the interceptor. Interceptor must implement this interface:

# lib/example_interceptor.rb
class ExampleInterceptor
  def initialize(elasticgraph_graphql:, config:)
    # `elasticgraph_graphql` is the `ElasticGraph::GraphQL` instance and has access to things like the
    # datastore client in case you need it in your interceptor. This can be useful if you need to perform
    # lookups on a derived index as part of your interceptor logic.
    #
    # `config` is a hash containing parameterized configuration values specific in the YAML settings
    # (see below for an example).
  end

  def intercept(query, field:, args:, http_request:, context:)
    # `query` is an `ElasticGraph::GraphQL::DatastoreQuery` that will be submitted to the datastore
    # as part of satisfying a GraphQL query.
    #
    # `field`, `args`, and `context` provide access to GraphQL query information which can be used
    # in your interceptor logic to influence how you modify the datastore query.
    #
    # `http_request` provides access to the original HTTP request and can likewise be used in your logic.
    #
    # Use `query.merge_with(...)` as desired to merge in query overrides like filters, or just return `query`.
    # (This method must return a query.)
  end
end

Finally, update your project's YAML config to enable this extension and configure it to use the interceptor:

diff --git a/config/settings/local.yaml b/config/settings/local.yaml
index 963f4f9..93df6f3 100644
--- a/config/settings/local.yaml
+++ b/config/settings/local.yaml
@@ -23,6 +23,15 @@ datastore:
 graphql:
   default_page_size: 50
   max_page_size: 500
+  extension_modules:
+  - require_path: elastic_graph/query_interceptor/graphql_extension
+    name: ElasticGraph::QueryInterceptor::GraphQLExtension
+query_interceptor:
+  interceptors:
+  - require_path: ./lib/example_interceptor
+    name: ExampleInterceptor
+    config: # Optional
+      foo: bar
 logger:
   device: stderr
 indexer:

As shown above, an optional "config" dictionary can be provided to pass in values to your interceptor when it is initialized.