.. _gfql-quick:

GFQL Quick Reference
====================

GFQL is the first fully vectorized dataframe-native graph query language with
an open-source GPU runtime. This quick reference page provides short examples
of various parameters and usage patterns.

If you are new to Cypher: Cypher is a graph query language popularized by
Neo4j and related tools. It uses ASCII-art graph patterns such as
``(n1)-[e1]->(n2)`` to describe node-edge-node traversals, so GFQL docs use
that notation as a familiar shorthand when discussing Cypher syntax through
``g.gfql("MATCH ...")``.

Basic Usage
-----------

**Chaining Operations**

.. doc-test: skip

.. code-block:: python

    g.gfql([...], engine=EngineAbstract.AUTO)

:meth:`gfql <graphistry.compute.gfql>` sequences multiple matchers for more complex patterns of paths and subgraphs

- **query**: Sequence of graph node/edge matchers and optional row-pipeline call steps (for example, `rows()`, `where_rows()`, `return_()`, `order_by()`, `limit()`), or an equivalent GFQL chain object.
- **engine**: Optional execution engine. Engine is typically not set, defaulting to `'auto'`. Use `'cudf'` for GPU acceleration and `'pandas'` for CPU.

Native GFQL chains are typed Python inputs. Pass the list, dict envelope, or
``Chain`` object itself; strings passed to ``g.gfql(...)`` are interpreted as
query text for the selected string language.

Use this page as a quick MATCH/chain reference.
For row-pipeline RETURN semantics, see :doc:`return`.

Choose The Right Entrypoint
---------------------------

- Use ``g.gfql([...])`` for native GFQL operators and
  ``g.gfql("MATCH ...")`` for Cypher syntax on the current graph.
- If a tool receives a serialized native chain such as ``"[{'type': ...}]"``,
  decode it into a real Python list/dict/``Chain`` before calling
  ``g.gfql(...)``. ``g.gfql(str)`` is reserved for Cypher query text by
  default.
- Use ``g.gfql_remote([...])`` for remote GFQL when the dataset size or
  hardware profile calls for remote execution, including remote GPU execution.
  See :ref:`gfql-remote`.

.. warning::
   `graphistry.cypher("...")` and `g.cypher("...")` are a separate remote database Cypher path
   (for example, Neo4j/Neptune integrations), not the GFQL execution surface summarized on this page.

Graph State Vs Row State
------------------------

- **Graph state** keeps a traversable graph in `_nodes` and `_edges`. Matchers, graph-preserving `call(...)` transforms, `let()` / `ref()` graph DAG stages, local Cypher `CALL graphistry.*.write()` queries, and local Cypher `GRAPH { MATCH ... }` constructors stay in graph state. (`GRAPH { }` is a GFQL extension — see :doc:`cypher` for details.)
- **Row state** stores tabular results in `_nodes` and uses an empty placeholder `_edges` frame. Row-pipeline steps such as `rows()`, `with_()`, `select()`, `return_()`, `group_by()`, and row-returning local Cypher `CALL ... YIELD ... RETURN ...` queries move into row state.
- A bare local Cypher procedure call without `.write()` also moves into row state. For example, `CALL graphistry.degree()` projects its default output columns into `_nodes` and clears `_edges`.
- If you want to enrich a graph and keep matching locally, use a graph-preserving `call()` / `let()` pattern or a bare local Cypher `CALL graphistry.*.write()`. The local Cypher compiler currently supports `graphistry.degree.write()` plus `graphistry.igraph.<alg>.write()` and `graphistry.cugraph.<alg>.write()` for algorithms exposed through `compute_igraph()` / `compute_cugraph()`, along with a curated NetworkX subset including `graphistry.nx.pagerank.write()`, `graphistry.nx.betweenness_centrality.write()`, `graphistry.nx.degree_centrality.write()`, `graphistry.nx.closeness_centrality.write()`, `graphistry.nx.eigenvector_centrality.write()`, `graphistry.nx.katz_centrality.write()`, `graphistry.nx.connected_components.write()`, `graphistry.nx.strongly_connected_components.write()`, `graphistry.nx.core_number.write()`, `graphistry.nx.hits.write()`, `graphistry.nx.edge_betweenness_centrality.write()`, and `graphistry.nx.k_core.write()`.

Cypher Strings Through ``g.gfql()``
-----------------------------------

Use ``g.gfql("MATCH ...")`` when you want Cypher syntax and declarative graph
semantics on a bound graph instead of writing the equivalent GFQL chain by
hand:

Do not pass stringified Python or JSON native-chain literals to
``g.gfql(...)``. Materialize those serialized values before calling GFQL, or
emit Cypher text intentionally.

.. doc-test: xfail

.. code-block:: python

    result = g.gfql(
        "MATCH (n1:Person)-[e1:FOLLOWS]->(n2:Person) "
        "RETURN n1.name AS source_name, n2.name AS target_name "
        "ORDER BY source_name, target_name "
        "LIMIT $top_n",
        params={"top_n": 5},
    )

    result._nodes

For the dedicated guide, helper APIs, and direct-vs-translation guidance, see
:doc:`cypher`.

Node Matchers
-------------

.. code-block:: python

  n(filter_dict=None, name=None, query=None)

:meth:`n <graphistry.compute.ast.n>` matches nodes based on their attributes.

- Filter nodes based on attributes.

- **Parameters**:

  - `filter_dict`: `{attribute: value}` or `{attribute: condition_function}`
  - `name`: Optional label; adds a boolean column in the result.
  - `query`: Custom query string (e.g., `"age > 30 and country == 'USA'"`).

**Examples:**

- Match nodes where `type` is `'person'`:

  .. code-block:: python

      n({"type": "person"})

- Match nodes with `age` greater than 30:

  .. code-block:: python

      n({"age": lambda x: x > 30})

- Use a custom query string:

  .. code-block:: python

      n(query="age > 30 and country == 'USA'")

Edge Matchers
-------------

.. code-block:: python

  e_forward(edge_match=None, hops=1, min_hops=None, max_hops=None, output_min_hops=None, output_max_hops=None, label_node_hops=None, label_edge_hops=None, label_seeds=False, to_fixed_point=False, source_node_match=None, destination_node_match=None, source_node_query=None, destination_node_query=None, edge_query=None, name=None)
  e_reverse(edge_match=None, hops=1, min_hops=None, max_hops=None, output_min_hops=None, output_max_hops=None, label_node_hops=None, label_edge_hops=None, label_seeds=False, to_fixed_point=False, source_node_match=None, destination_node_match=None, source_node_query=None, destination_node_query=None, edge_query=None, name=None)
  e_undirected(edge_match=None, hops=1, min_hops=None, max_hops=None, output_min_hops=None, output_max_hops=None, label_node_hops=None, label_edge_hops=None, label_seeds=False, to_fixed_point=False, source_node_match=None, destination_node_match=None, source_node_query=None, destination_node_query=None, edge_query=None, name=None)
  
  # alias for e_undirected
  e(edge_match=None, hops=1, min_hops=None, max_hops=None, output_min_hops=None, output_max_hops=None, label_node_hops=None, label_edge_hops=None, label_seeds=False, to_fixed_point=False, source_node_match=None, destination_node_match=None, source_node_query=None, destination_node_query=None, edge_query=None, name=None)

:meth:`e <graphistry.compute.ast.e>` matches edges based on their attributes (undirected). May also include matching on edge's source and destination nodes.

- Traverse edges in the forward direction.

- **Parameters**:

  - `edge_match`: `{attribute: value}` or `{attribute: condition_function}`
  - `edge_query`: Custom query string for edge attributes.
  - `hops`: `int`, number of hops to traverse.
  - `min_hops`/`max_hops`: Inclusive traversal bounds (min defaults to 1 unless max_hops is 0; max defaults to `hops`).
  - `output_min_hops`/`output_max_hops`: Optional post-filter slice; defaults keep all traversed hops up to `max_hops`.
  - `label_node_hops`/`label_edge_hops`: Optional column names for hop numbers; `label_seeds=True` adds hop 0 for seeds.
  - `to_fixed_point`: `bool`, continue traversal until no more matches.
  - `source_node_match`: Filter for source nodes.
  - `destination_node_match`: Filter for destination nodes.
  - `source_node_query`: Custom query string for source nodes.
  - `destination_node_query`: Custom query string for destination nodes.
  - `name`: Optional label.

**Examples:**

- Traverse up to 2 hops forward on edges where `status` is `'active'`:

  .. code-block:: python

      e_forward({"status": "active"}, hops=2)

- Traverse 2..4 hops but show only hops 3..4 with labels:

  .. code-block:: python

      e_forward(
          {"status": "active"},
          min_hops=2,
          max_hops=4,
          output_min_hops=3,
          label_edge_hops="edge_hop"
      )

- Use custom edge query strings:

  .. code-block:: python

      e_forward(edge_query="weight > 5 and type == 'connects'")

- Filter source and destination nodes with match dictionaries:

  .. code-block:: python

      e_forward(
          source_node_match={"status": "active"},
          destination_node_match={"age": lambda x: x < 30}
      )

- Filter source and destination nodes with queries:

  .. code-block:: python

      e_forward(
          source_node_query="status == 'active'",
          destination_node_query="age < 30"
      )

- Label matched edges:

  .. code-block:: python

      e_forward(name="active_edges")

:class:`e_reverse <graphistry.compute.ast.e_reverse>`, :class:`e_forward <graphistry.compute.ast.e_forward>`, and :class:`e <graphistry.compute.ast.e>` are aliases.

- :class:`e_reverse <graphistry.compute.ast.e_reverse>`: Same as :class:`e_forward <graphistry.compute.ast.e_forward>`, but traverses in reverse.
- :class:`e <graphistry.compute.ast.e>`: Traverses edges regardless of direction.

Predicates
-----------

:class:`graphistry.compute.predicates.ASTPredicate.ASTPredicate`

- Matches using a predicate on entity attributes.

See :doc:`predicates/quick` for more information.

**Example:**

- Match nodes where `category` is `'A'`, `'B'`, or `'C'`:

  .. code-block:: python

      from graphistry import n, is_in

      n({"category": is_in(["A", "B", "C"])})

Where (Same-Path Constraints)
-----------------------------

Use `where` to relate attributes across named steps in a chain.

.. code-block:: python

    from graphistry import n, e_forward, col, compare

    g.gfql(
        [
            n({"type": "account"}, name="a"),
            e_forward(name="e"),
            n({"type": "user"}, name="c"),
        ],
        where=[
            compare(col("a", "owner_id"), "==", col("c", "owner_id")),
            compare(col("e", "org_id"), "==", col("a", "org_id")),
        ],
    )

`compare()` can relate node and edge columns when the column types align.
Supported `compare(..., op, ...)` operators: `==`, `!=`, `<`, `<=`, `>`, `>=`.
WHERE works with `g.gfql([...], where=[...])`; `Chain(..., where=[...])` is the
equivalent explicit form.
Multiple WHERE comparisons are ANDed.

Row Pipelines (`MATCH ... RETURN` style)
----------------------------------------

Use row-pipeline operators to move from pattern matching to tabular Cypher-like
`RETURN` processing.

.. code-block:: python

    from graphistry import n, e_forward, gt
    from graphistry.compute import rows, where_rows, return_, order_by, limit

    top_people = g.gfql([
        n({"type": "Person"}),
        e_forward({"type": "FOLLOWS"}),
        n({"type": "Person", "score": gt(10)}, name="p"),
        rows(table="nodes", source="p"),
        where_rows(expr="score >= 50 AND name STARTS WITH 'A'"),
        return_(["id", "name", ("score_bucket", "score / 10")]),
        order_by([("score_bucket", "desc"), ("name", "asc")]),
        limit(25),
    ])

    top_people._nodes

Notes:

- `rows(table="nodes" or table="edges", source="alias")` picks the active row table.
  `source` must be an alias introduced earlier via `name="..."` on a matcher.
  In the example above, `source="p"` refers to `n(..., name="p")`, so only
  rows matched by alias `p` are used.
- Edge example: `e_forward(..., name="e")` followed by
  `rows(table="edges", source="e")` scopes rows to edges matched as `e`.
- If `source` is omitted (for example, `rows(table="nodes")`), the full active
  nodes/edges table is used.
- `return_(["col"])` is shorthand for `return_([("col", "col")])`.
- `with_(...)` and `select(...)` share projection semantics with `return_(...)`:

  .. code-block:: python

      from graphistry.compute import rows, with_, select, return_

      # Equivalent projections
      rows(table="nodes", source="p")
      return_(["id", ("score2", "score * 2")])
      with_(["id", ("score2", "score * 2")])
      select([("id", "id"), ("score2", "score * 2")])

  `return_(["id"])`, `with_(["id"])`, and `select([("id", "id")])` all project
  the same `id` column.
- `where_rows()` evaluates row expressions and filter dictionaries in a
  vectorized dataframe execution path (pandas/cuDF engines).
- In `where_rows(expr="...")`, supported comparison operators are
  `=`, `!=`, `<>`, `<`, `<=`, `>`, `>=`.

Combined Examples
-----------------

- **MATCH with same-path WHERE constraints:**

  .. code-block:: python

      from graphistry import n, e_forward, col, compare

      g.gfql(
          [
              n({"type": "account"}, name="a"),
              e_forward({"status": "active"}, name="e"),
              n({"type": "user"}, name="u"),
          ],
          where=[compare(col("a", "org_id"), "==", col("u", "org_id"))],
      )

- **MATCH then RETURN (row pipeline):**

  .. code-block:: python

      from graphistry import n, e_forward, gt
      from graphistry.compute import rows, where_rows, return_, order_by, limit

      g.gfql([
          n({"type": "Person"}),
          e_forward({"type": "FOLLOWS"}),
          n({"type": "Person", "score": gt(0)}, name="p"),
          rows(table="nodes", source="p"),
          where_rows(expr="score >= 50"),
          return_(["id", "name", "score"]),
          order_by([("score", "desc"), ("name", "asc")]),
          limit(10),
      ])

- **Find people connected to transactions via active relationships:**

  .. code-block:: python

      g.gfql([
          n({"type": "person"}),
          e_forward({"status": "active"}),
          n({"type": "transaction"})
      ])

- **Label nodes and edges during traversal:**

  .. code-block:: python

      g.gfql([
          n({"id": "start_node"}, name="start"),
          e_forward(name="edge1"),
          n({"level": 2}, name="middle"),
          e_forward(name="edge2"),
          n({"type": "end_type"}, name="end")
      ])

- **Traverse until no more matches (fixed point):**

  .. code-block:: python

      g.gfql([
          n({"status": "infected"}),
          e_forward(to_fixed_point=True),
          n(name="reachable")
      ])

- **Filter by multiple conditions:**

  .. code-block:: python

      g.gfql([
          n({"type": is_in(["server", "database"])}),
          e_undirected({"protocol": "TCP"}, hops=3),
          n(query="risk_level >= 8")
      ])

- **Use custom queries in matchers:**

  .. code-block:: python

      g.gfql([
          n(query="age > 30 and country == 'USA'"),
          e_forward(edge_query="weight > 5"),
          n(query="status == 'active'")
      ])

GPU Acceleration
----------------

- **Enable GPU mode:**

  .. code-block:: python

      g.gfql([...], engine='cudf')

- **Example with cuDF DataFrames:**

  .. code-block:: python

      import cudf

      e_gdf = cudf.from_pandas(edge_df)
      n_gdf = cudf.from_pandas(node_df)

      g = graphistry.nodes(n_gdf, 'node_id').edges(e_gdf, 'src', 'dst')
      g.gfql([...], engine='cudf')

Remote Mode
-----------

- **Query existing remote data**

  .. code-block:: python

      g = graphistry.bind(dataset_id='ds-abc-123')

      nodes_df = g.gfql_remote([n()])._nodes

- **Upload graph and run GFQL**

  .. code-block:: python

      g2 = g1.upload()

      g3 = g2.gfql_remote([n(), e(), n()])

- **Enforce CPU and GPU mode on remote GFQL**

  .. code-block:: python

      g3a = g2.gfql_remote([n(), e(), n()], engine='pandas') 
      g3b = g2.gfql_remote([n(), e(), n()], engine='cudf')

- **Return only nodes and certain columns**

  .. code-block:: python

      cols = ['id', 'name']
      g2b = g1.gfql_remote([n(), e(), n()], output_type="edges", edge_col_subset=cols)

- **Return only edges and certain columns**

  .. code-block:: python

      cols = ['src', 'dst']
      g2b = g1.gfql_remote([n(), e(), n()], output_type="edges", edge_col_subset=cols)

- **Return only shape metadata**

  .. code-block:: python

      shape_df = g1.chain_remote_shape([n(), e(), n()])

- **Run remote Python and get back a graph**

  .. code-block:: python

      def my_remote_trim_graph_task(g):
          return (g
              .nodes(g._nodes[:10])
              .edges(g._edges[:10])
          )

      g2 = g1.upload()
      g3 = g2.python_remote_g(my_remote_trim_graph_task)

- **Run remote Python and get back a table**

  .. code-block:: python

      def first_n_edges(g):
          return g._edges[:10]

      some_edges_df = g.python_remote_table(first_n_edges)

- **Run remote Python and get back JSON**

  .. code-block:: python

      def first_n_edges(g):
          return g._edges[:10].to_json()

      some_edges_json = g.python_remote_json(first_n_edges)

- **Run remote Python and ensure runs on CPU or GPU**

  .. code-block:: python

      g3a = g2.python_remote_g(my_remote_trim_graph_task, engine='pandas')
      g3b = g2.python_remote_g(my_remote_trim_graph_task, engine='cudf')

- **Run remote Python, passing as a string**

  .. code-block:: python

      g2 = g1.upload()

      # ensure method is called "task" and takes a single argument "g"
      g3 = g2.python_remote_g("""
          def task(g):
              return (g
                  .nodes(g._nodes[:10])
                  .edges(g._edges[:10])
              )
      """)

Let Bindings and DAG Patterns
-----------------------------

Use Let bindings to create directed acyclic graph (DAG) patterns with named operations. Lists are treated as implicit Chains.

- **Basic Let with named bindings:**

  .. code-block:: python

      from graphistry import let, ref, n, e_forward, gt

      result = g.gfql(let({
          'suspects': n({'risk_score': gt(80)}),
          'connections': [
              n({'risk_score': gt(80)}),
              e_forward({'type': 'transaction'}),
              n()
          ]
      }))

      # Access results by name
      suspects = result._nodes[result._nodes['suspects']]
      connections = result._edges[result._edges['connections']]

- **Complex DAG with multiple references:**

  .. code-block:: python

      from graphistry import let, ref, n, e_forward, gt

      result = g.gfql(let({
          'high_value': n({'balance': gt(100000)}),
          'large_transfers': [
              n({'balance': gt(100000)}),
              e_forward({'type': 'transfer', 'amount': gt(10000)}),
              n()
          ],
          'suspicious': ref('large_transfers', [
              n({'created_recent': True, 'verified': False})
          ])
      }))

Call Operations
---------------

Run graph algorithms like PageRank, community detection, and layouts directly within your GFQL queries:

- **Compute PageRank:**

  .. code-block:: python

      from graphistry import call, let, ref, n, e

      # Use let() to compose filter + enrichment
      result = g.gfql(let({
          'persons': [n({'type': 'person'}), e(), n()],
          'ranked': ref('persons', [call('compute_cugraph', {'alg': 'pagerank', 'damping': 0.85})])
      }))

      # Results have pagerank column
      top_nodes = result._nodes.sort_values('pagerank', ascending=False).head(10)

- **Enrich a graph, then keep matching:**

  .. code-block:: python

      g_enriched = g.gfql("CALL graphistry.degree.write()")
      assert not g_enriched._edges.empty
      top_degree = g_enriched.gfql(
          "MATCH (n) WHERE n.degree >= 2 RETURN n.id AS id, n.degree AS degree ORDER BY degree DESC LIMIT 10"
      )

  Local note: a bare `g.gfql("CALL graphistry.*.write()")` stays in graph state and can feed later `MATCH` queries.
  `g.gfql("CALL ... YIELD ... RETURN ...")` still targets row-returning procedure flows.

- **Return procedure rows instead of an enriched graph:**

  .. code-block:: python

      degree_rows = g.gfql("CALL graphistry.degree()")
      assert degree_rows._edges.empty

      # Row state: _nodes has nodeId/degree columns and _edges is empty
      degree_rows._nodes

- **Community detection with Louvain:**

  .. code-block:: python

      from graphistry import call, let, ref, n, e_forward

      # Use let() to compose traversal + community detection
      result = g.gfql(let({
          'reachable': [n({'active': True}), e_forward(to_fixed_point=True), n()],
          'communities': ref('reachable', [call('compute_cugraph', {'alg': 'louvain'})])
      }))

      # Results have community column
      communities = result._nodes.groupby('community').size()

- **Filter and compute within Let:**

  .. code-block:: python

      from graphistry import call, let, ref, n, e, gt

      # Split mixed chain into separate bindings
      result = g.gfql(let({
          'suspects': [n({'flagged': True}), e(), n()],
          'ranked': ref('suspects', [
              call('compute_cugraph', {'alg': 'pagerank'})
          ]),
          'influencers': ref('ranked', [
              n({'pagerank': gt(0.01)})
          ])
      }))

- **Apply layout algorithms:**

  .. code-block:: python

      from graphistry import call, let, ref, n, e_forward, is_in

      # Use let() to compose traversal + layout
      result = g.gfql(let({
          'entities': [n({'type': is_in(['person', 'company'])}), e_forward(), n()],
          'positioned': ref('entities', [call('fa2_layout', {'iterations': 100})])
      }))

      # Results have x, y coordinates for visualization
      result.plot()

Tip: For subset-based coloring after GFQL, use ``result.collections(...)`` and see
:doc:`/visualization/layout/settings`.

Remote Graph References
-----------------------

Reference graphs on remote servers for distributed computing:

- **Basic remote reference:**

  .. code-block:: python

      from graphistry.compute import remote

      result = g.gfql([
          remote(dataset_id='fraud-network-2024'),
          n({'risk_score': gt(90)}),
          e_forward()
      ])

- **Combine remote and local data in Let:**

  .. code-block:: python

      from graphistry.compute import remote

      result = g.gfql(let({
          'remote_data': remote(dataset_id='historical-2023'),
          'high_risk': ref('remote_data', [
              n({'risk_score': gt(95)})
          ]),
          'connections': ref('remote_data', [
              n({'risk_score': gt(95)}),
              e_forward({'type': 'transaction'}),
              n()
          ])
      }))

Advanced Usage
--------------

- **Traversal with source and destination node filters and queries:**

  .. code-block:: python

      e_forward(
          edge_query="type == 'follows' and weight > 2",
          source_node_match={"status": "active"},
          destination_node_query="age < 30",
          hops=2,
          name="social_edges"
      )

- **Node matcher with all parameters:**

  .. code-block:: python

      n(
          filter_dict={"department": "sales"},
          query="age > 25 and tenure > 2",
          name="experienced_sales"
      )

- **Edge matcher with all parameters:**

  .. code-block:: python

      e_reverse(
          edge_match={"transaction_type": "refund"},
          edge_query="amount > 100",
          source_node_match={"status": "inactive"},
          destination_node_match={"region": "EMEA"},
          name="large_refunds"
      )

Parameter Summary
-----------------

- **Common Parameters:**

  - `filter_dict`: Attribute filters (e.g., `{"status": "active"}`)
  - `query`: Custom query string (e.g., `"age > 30"`)
  - `hops`: Max hops to traverse (shorthand for `max_hops`, default `1`)
  - `to_fixed_point`: Continue traversal until no more matches (`bool`, default `False`)
  - `name`: Label for matchers (`str`)
  - `source_node_match`, `destination_node_match`: Filters for connected nodes
  - `source_node_query`, `destination_node_query`: Queries for connected nodes
  - `edge_match`: Filters for edges
  - `edge_query`: Query for edges
  - `engine`: Execution engine (`EngineAbstract.AUTO`, `'cudf'`, etc.)

Traversal Directions
--------------------

- **Forward Traversal:** `e_forward(...)`
- **Reverse Traversal:** `e_reverse(...)`
- **Undirected Traversal:** `e_undirected(...)`

Tips and Best Practices
-----------------------

- **Limit hops for performance:** Specify `hops` to control traversal depth.
- **Use naming for analysis:** Apply `name` to label and filter results.
- **Combine filters:** Use `filter_dict` and `query` for precise matching.
- **Leverage GPU acceleration:** Use `engine='cudf'` for large datasets.
- **Avoid infinite loops:** Be cautious with `to_fixed_point=True` in cyclic graphs.

Examples at a Glance
--------------------

- **Find all paths between two nodes:**

  .. code-block:: python

      g.gfql([
          n({g._node: "Alice"}),
          e_undirected(hops=3),
          n({g._node: "Bob"})
      ])

- **Match nodes with IDs in a range:**

  .. code-block:: python

      n(query="100 <= id <= 200")

- **Traverse edges with specific labels:**

  .. code-block:: python

      e_forward({"label": is_in(["knows", "likes"])})

- **Identify subgraphs based on attributes:**

  .. code-block:: python

      g.gfql([
          n({"community": "A"}),
          e_undirected(hops=2),
          n({"community": "B"}, name="bridge_nodes")
      ])

- **Custom edge and node queries:**

  .. code-block:: python

      g.gfql([
          n(query="age >= 18"),
          e_forward(edge_query="interaction == 'message'"),
          n(query="location == 'NYC'")
      ])
