> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/graphprotocol/graph-node/llms.txt
> Use this file to discover all available pages before exploring further.

# Querying

> How Graph Node processes and optimizes GraphQL queries

Querying is how applications retrieve indexed blockchain data from Graph Node. Graph Node automatically generates a GraphQL API for each subgraph and translates queries into optimized SQL that executes against PostgreSQL.

## Query Overview

Graph Node provides a rich GraphQL API for each deployed subgraph without requiring any additional code. The API is automatically generated from the subgraph's schema and supports:

* **Filtering**: Filter entities by any field
* **Sorting**: Order results by any field
* **Pagination**: Efficient cursor-based and offset-based pagination
* **Nested queries**: Load related entities in a single query
* **Time-travel**: Query historical state at any block
* **Full-text search**: Search text fields with ranking
* **Aggregations**: Compute sums, counts, averages, etc.

<Note>
  All queries are read-only. Graph Node does not support mutations via GraphQL - data is only modified by indexing blockchain events.
</Note>

## Query Endpoints

Subgraphs are queryable via HTTP POST to:

```
http://localhost:8000/subgraphs/name/<subgraph-name>
http://localhost:8000/subgraphs/id/<deployment-ipfs-hash>
```

### GraphQL Playground

Open the endpoint in a browser to access GraphiQL, an interactive query playground with:

* Schema documentation
* Autocomplete
* Query validation
* Result visualization

## Query Structure

Queries follow standard GraphQL syntax and are automatically validated against the subgraph's schema.

### Basic Query

```graphql theme={null}
{
  users(first: 10, where: {balance_gt: "1000000000000000000"}) {
    id
    address
    balance
    createdAt
  }
}
```

### Nested Queries

Load related entities in a single request:

```graphql theme={null}
{
  users(first: 5, orderBy: balance, orderDirection: desc) {
    id
    address
    balance
    transactions(first: 10, orderBy: timestamp, orderDirection: desc) {
      id
      hash
      value
      timestamp
      to {
        id
        address
      }
    }
  }
}
```

### Query by ID

Fetch a specific entity by its ID:

```graphql theme={null}
{
  user(id: "0x1234...") {
    id
    address
    balance
  }
}
```

## Query Features

### Filtering

Graph Node generates comprehensive filter arguments for every entity field:

<Accordion title="Comparison Operators">
  Available for numeric, string, and byte fields:

  * `field`: Equals
  * `field_not`: Not equals
  * `field_gt`: Greater than
  * `field_gte`: Greater than or equal
  * `field_lt`: Less than
  * `field_lte`: Less than or equal
  * `field_in`: In array
  * `field_not_in`: Not in array

  ```graphql theme={null}
  {
    users(where: {
      balance_gte: "1000000000000000000"
      balance_lt: "10000000000000000000"
    }) {
      id
      balance
    }
  }
  ```
</Accordion>

<Accordion title="String Operators">
  Additional operators for string fields:

  * `field_contains`: Contains substring
  * `field_not_contains`: Does not contain substring
  * `field_starts_with`: Starts with prefix
  * `field_not_starts_with`: Does not start with prefix
  * `field_ends_with`: Ends with suffix
  * `field_not_ends_with`: Does not end with suffix

  ```graphql theme={null}
  {
    tokens(where: {
      name_contains: "USD"
      symbol_starts_with: "W"
    }) {
      id
      name
      symbol
    }
  }
  ```
</Accordion>

<Accordion title="Logical Operators">
  Combine multiple conditions:

  * `and`: All conditions must match (implicit)
  * `or`: At least one condition must match

  ```graphql theme={null}
  {
    users(where: {
      or: [
        { balance_gt: "10000000000000000000" }
        { transactions_: { value_gt: "1000000000000000000" } }
      ]
    }) {
      id
      balance
    }
  }
  ```

  **Implementation:** `store/postgres/src/relational_queries.rs` translates to SQL WHERE clause
</Accordion>

<Accordion title="Relationship Filters">
  Filter by related entity fields using `_` suffix:

  ```graphql theme={null}
  {
    transactions(where: {
      from_: { balance_gt: "1000000000000000000" }
      to_: { address: "0x1234..." }
    }) {
      id
      value
      from {
        address
      }
      to {
        address
      }
    }
  }
  ```

  **SQL generation:** Generates JOINs to related tables
</Accordion>

### Sorting

Sort results by any field:

```graphql theme={null}
{
  users(
    orderBy: balance
    orderDirection: desc
    first: 10
  ) {
    id
    balance
  }
}
```

**Parameters:**

* `orderBy`: Field to sort by
* `orderDirection`: `asc` (ascending) or `desc` (descending)

**Multi-field sorting:** Not yet supported in API, but planned

### Pagination

Graph Node supports two pagination strategies:

<Accordion title="Forward Pagination">
  Use `first` and `skip` for offset-based pagination:

  ```graphql theme={null}
  # Page 1
  { users(first: 10, skip: 0) { id } }

  # Page 2
  { users(first: 10, skip: 10) { id } }

  # Page 3
  { users(first: 10, skip: 20) { id } }
  ```

  **Limitation:** Large offsets are inefficient (must scan all skipped rows)

  **Best for:** Small datasets or low page numbers
</Accordion>

<Accordion title="Cursor-Based Pagination">
  Use `first` with `where` filter for efficient pagination:

  ```graphql theme={null}
  # Page 1
  {
    users(first: 10, orderBy: id) {
      id
      address
    }
  }

  # Page 2 (using last ID from page 1)
  {
    users(
      first: 10
      orderBy: id
      where: { id_gt: "last_id_from_page_1" }
    ) {
      id
      address
    }
  }
  ```

  **Benefit:** Consistent performance regardless of page depth

  **Best for:** Large datasets and deep pagination
</Accordion>

### Time-Travel Queries

Query historical state at any block number:

```graphql theme={null}
{
  users(
    block: { number: 15000000 }
    first: 10
  ) {
    id
    balance  # Balance at block 15000000
  }
}
```

**How it works:** Block range filter in SQL:

```sql theme={null}
WHERE block_range @> 15000000
```

This leverages the block range stored with each entity version.

**Location in codebase:** `store/postgres/src/block_range.rs`

### Full-Text Search

Search text fields with relevance ranking (when enabled in schema):

```graphql theme={null}
{
  articleSearch(text: "blockchain ethereum") {
    id
    title
    content
    rank  # Relevance score
  }
}
```

**Schema definition:**

```graphql theme={null}
type Article @entity {
  id: ID!
  title: String! @fulltext(name: "articleSearch", language: en)
  content: String! @fulltext(name: "articleSearch")
}
```

**Implementation:** Uses PostgreSQL full-text search features

**Reference:** `docs/aggregations.md`

### Aggregations

Compute aggregate values across entities:

```graphql theme={null}
{
  userAggregates {
    count
    sum {
      balance
    }
    avg {
      balance
    }
    min {
      balance
    }
    max {
      balance
    }
  }
}
```

**Available aggregations:**

* `count`: Number of entities
* `sum`: Sum of numeric fields
* `avg`: Average of numeric fields
* `min`: Minimum value
* `max`: Maximum value

**Reference:** `docs/aggregations.md`

### Meta Field

Query indexing metadata:

```graphql theme={null}
{
  _meta {
    block {
      number
      hash
      timestamp
    }
    deployment
    hasIndexingErrors
  }
}
```

**Use cases:**

* Check indexing progress
* Verify data freshness
* Detect indexing errors

## Query Processing Pipeline

Graph Node translates GraphQL queries into SQL through several stages:

```
┌─────────────────┐
│  GraphQL Query  │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Parse & Validate│
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Query Planning │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ SQL Generation  │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Execute Query  │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ Format Response │
└─────────────────┘
```

### 1. Parsing and Validation

The GraphQL query is parsed into an AST and validated:

```rust theme={null}
pub struct Query {
    // Parsed GraphQL document
    document: Document,
    // Subgraph's schema
    schema: Arc<ApiSchema>,
    // Query root type
    root_type: String,
}
```

**Validation checks:**

* Query syntax is valid GraphQL
* All fields exist in schema
* Arguments are correctly typed
* Required arguments are present

**Location in codebase:** `graphql/src/execution/query.rs`

### 2. Query Planning

The query planner determines optimal execution strategy:

* **Entity resolution**: Map GraphQL types to database tables
* **Filter analysis**: Identify indexes that can be used
* **Join planning**: Optimize relationship traversals
* **Pagination strategy**: Choose offset vs range-based pagination

### 3. SQL Generation

GraphQL query is translated to SQL:

```rust theme={null}
pub trait QueryFragment {
    fn walk_ast<'a>(&'a self, pass: AstPass<'a>);
}
```

The SQL generator:

* Constructs SELECT with requested fields
* Adds JOIN clauses for relationships
* Applies WHERE filters
* Adds ORDER BY and LIMIT clauses
* Includes block range filters for time-travel

**Example translation:**

```graphql theme={null}
{
  users(first: 5, where: {balance_gt: "1000"}, orderBy: balance) {
    id
    balance
    transactions(first: 3) {
      value
    }
  }
}
```

**Generated SQL:**

```sql theme={null}
-- Main query
SELECT u.id, u.balance
FROM sgd1.user u
WHERE u.balance > 1000
  AND u.block_range @> 2147483647  -- Current block range
ORDER BY u.balance
LIMIT 5;

-- For each user, fetch transactions
SELECT t.value, t."from"
FROM sgd1.transaction t
WHERE t."from" = ANY($1)  -- Array of user IDs
  AND t.block_range @> 2147483647
LIMIT 3;
```

**Location in codebase:** `store/postgres/src/relational_queries.rs`

### 4. Query Execution

SQL executes against PostgreSQL:

```rust theme={null}
pub trait QueryStore: Send + Sync {
    fn find_query_values(
        &self,
        query: EntityQuery,
    ) -> Result<Vec<BTreeMap<Word, Value>>, QueryExecutionError>;
}
```

**Connection pooling:** Each shard has a connection pool sized by configuration

**Read replicas:** Queries are distributed across replicas based on weights

**Location in codebase:** `store/postgres/src/query_store.rs`

### 5. Response Formatting

SQL results are transformed into GraphQL response:

* **Deserialization**: PostgreSQL types → Rust types → GraphQL types
* **Relationship assembly**: Join nested entities from separate queries
* **Type coercion**: Apply GraphQL type rules
* **JSON formatting**: Convert to GraphQL JSON response

## Query Optimization

### Indexing Strategy

PostgreSQL indexes are critical for query performance:

**Automatic indexes:**

* Primary key (`id`)
* Version ID (`vid`)
* Block range (`block_range`) using GiST index

**Generated index example:**

```sql theme={null}
CREATE INDEX user_id_block_range_excl 
  ON sgd1.user 
  USING gist (id, block_range);

CREATE INDEX user_block_range_idx
  ON sgd1.user
  USING gist (block_range);
```

**Location in codebase:** `store/postgres/src/relational.rs:generate_indexes()`

### Query Complexity Limits

Graph Node enforces limits to prevent resource exhaustion:

* **Max query depth**: Limits nested query levels (default: 8)
* **Max result size**: Limits returned entities (default: 1000 per query)
* **Query timeout**: Maximum execution time (configurable)

**Configuration:**

```bash theme={null}
GRAPH_GRAPHQL_MAX_DEPTH=15
GRAPH_GRAPHQL_MAX_COMPLEXITY=1000000
GRAPH_GRAPHQL_QUERY_TIMEOUT=60
```

### Query Caching

GraphNode includes query result caching:

* **Cache key**: Query hash + block number
* **Invalidation**: Automatic on new blocks
* **Storage**: In-memory cache per query node

**Configuration:**

```bash theme={null}
GRAPH_QUERY_CACHE_BLOCKS=2
GRAPH_QUERY_CACHE_MAX_MEM=1000  # MB
```

### Prepared Statements

Frequently-executed queries use prepared statements:

* **Query plan reuse**: Avoids repeated planning overhead
* **Parameter binding**: Safely inject filter values
* **Connection affinity**: Same connection reuses prepared statement

### Pagination Performance

<Note>
  **Best practice:** Use cursor-based pagination with indexed fields for large result sets. Avoid `skip` with large offsets.
</Note>

**Inefficient:**

```graphql theme={null}
{ users(first: 10, skip: 10000) { id } }
```

**Efficient:**

```graphql theme={null}
{ users(first: 10, where: { id_gt: "<last_id>" }, orderBy: id) { id } }
```

## Query Performance Monitoring

### Metrics

Graph Node exposes query metrics:

* `query_execution_time`: Query duration histogram
* `query_validation_time`: Validation time
* `query_cache_hit_rate`: Cache effectiveness
* `query_parsing_errors`: Failed query parsing

**Endpoint:** `http://localhost:8040/metrics`

### Query Logging

Enable query logging for debugging:

```bash theme={null}
GRAPH_LOG_QUERY_TIMING=gql
```

Logs include:

* Query text
* Execution time
* Number of results
* SQL execution time

### Slow Query Analysis

Identify slow queries:

```bash theme={null}
# Enable PostgreSQL slow query log
GRAPH_SQL_STATEMENT_TIMEOUT=30000  # 30 seconds
```

Analyze with PostgreSQL:

```sql theme={null}
-- Find slow queries
SELECT query, mean_exec_time, calls
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;
```

## GraphQL Schema Generation

Graph Node automatically generates a rich GraphQL schema from the subgraph's entity schema.

### Generated Types

For each entity, Graph Node generates:

**Query types:**

* `entity(id: ID!)`: Fetch by ID
* `entities(...)`: Fetch multiple with filtering

**Filter input types:**

* `Entity_filter`: All available filters for entity

**Order types:**

* `Entity_orderBy`: Enum of sortable fields

**Example:**

```graphql theme={null}
type Query {
  user(id: ID!, block: Block_height): User
  users(
    first: Int
    skip: Int
    orderBy: User_orderBy
    orderDirection: OrderDirection
    where: User_filter
    block: Block_height
  ): [User!]!
}

input User_filter {
  id: ID
  id_not: ID
  id_gt: ID
  id_lt: ID
  id_gte: ID
  id_lte: ID
  id_in: [ID!]
  id_not_in: [ID!]
  balance: BigInt
  balance_not: BigInt
  balance_gt: BigInt
  balance_lt: BigInt
  balance_gte: BigInt
  balance_lte: BigInt
  # ... more fields
}

enum User_orderBy {
  id
  address
  balance
  createdAt
  updatedAt
}
```

**Location in codebase:** `graphql/src/schema/` generates schema from entity definitions

## Advanced Query Patterns

### Union Queries

Query multiple entity types (when schema supports interfaces):

```graphql theme={null}
{
  activities(first: 10) {
    ... on Transfer {
      from
      to
      value
    }
    ... on Approval {
      owner
      spender
      value
    }
  }
}
```

### Subscriptions

WebSocket-based real-time subscriptions:

```graphql theme={null}
subscription {
  users(where: {balance_gt: "1000000"}) {
    id
    balance
  }
}
```

**Implementation:** Server pushes updates when entities change

**Endpoint:** `ws://localhost:8001/subgraphs/name/<name>`

## Query Best Practices

<CardGroup cols={2}>
  <Card title="Request Only Needed Fields" icon="minimize">
    Don't fetch unnecessary data. Specify exact fields required.
  </Card>

  <Card title="Use Cursor Pagination" icon="arrows-left-right">
    Prefer cursor-based pagination over large offsets for better performance.
  </Card>

  <Card title="Limit Nested Queries" icon="layer-group">
    Deep nesting impacts performance. Flatten queries when possible.
  </Card>

  <Card title="Filter Early" icon="filter">
    Apply filters at the earliest level to reduce data processed.
  </Card>
</CardGroup>

## Troubleshooting

### Query Too Complex

**Error:** "Query exceeds maximum complexity"

**Solution:**

* Reduce query depth
* Fetch fewer entities per query
* Split into multiple smaller queries
* Increase `GRAPH_GRAPHQL_MAX_COMPLEXITY`

### Query Timeout

**Error:** "Query timeout"

**Solution:**

* Optimize filters (ensure indexed fields)
* Reduce result size with `first`
* Check PostgreSQL for missing indexes
* Increase `GRAPH_GRAPHQL_QUERY_TIMEOUT`

### Inconsistent Results

**Cause:** Querying during active indexing

**Solution:**

* Check `_meta.hasIndexingErrors`
* Wait for subgraph to sync (`_meta.block.number`)
* Use `block: {number: X}` for consistent snapshots

## Next Steps

<CardGroup cols={2}>
  <Card title="Indexing" icon="gears" href="/core-concepts/indexing">
    Understand how data is indexed before querying
  </Card>

  <Card title="Architecture" icon="sitemap" href="/core-concepts/architecture">
    Learn about Graph Node's overall architecture
  </Card>

  <Card title="Subgraphs" icon="book" href="/core-concepts/subgraphs">
    Deep dive into subgraph structure and manifests
  </Card>

  <Card title="SQL Query Generation" icon="database" href="https://github.com/graphprotocol/graph-node/blob/master/docs/implementation/sql-query-generation.md">
    Technical details on SQL generation
  </Card>
</CardGroup>
