# Analyzing queries

TypeDB allows the user to "analyze" a query without actually executing it. Analysis simply parses a query, then type-checks it, allowing a user to check for syntax and typing errors against a schema.

The envisioned use is to facilitate developer tooling around TypeDB, such as plugins to validate TypeQL queries when application code is being compiled, and debugging type errors. Such tools can also be used by AI query-generators to automatically validate generated queries.

This page gives an overview of how to analyze a query and use the response. The examples are valid python code using the TypeDB python driver. The class definitions are illustrative.

## [](#_analyzing_a_query)Analyzing a query

Analyzing a query follows the same pattern as [running a query](../queries/index.md).

```python
#!test[]
#{{
from typedb.driver import *

DB_NAME = "my_database"
address = "localhost:1729"
credentials = Credentials("admin", "password")
options = DriverOptions(DriverTlsConfig.disabled())

driver = TypeDB.driver(address, credentials, options)
try:
    driver.databases.create(DB_NAME) # raises already exists exception
finally:
    pass

with driver.transaction(DB_NAME, TransactionType.SCHEMA) as tx:
    tx.query("""
        define
        attribute company-name, value string;
		attribute email, value string;
		attribute name, value string;
		attribute age, value integer;
        entity company, owns company-name, owns email, plays employment:employer;
		entity user, owns name, owns email, owns age, plays employment:employee;
        relation employment, relates employer, relates employee;
    """).resolve()
    tx.commit()

with driver.transaction(DB_NAME, TransactionType.WRITE) as tx:
    tx.query("insert $u isa user, has name 'alice', has email 'alice@example.com', has age 25;").resolve()
    tx.commit()

#}}
with driver.transaction(DB_NAME, TransactionType.READ) as tx:
    # 1. Send the analyze request
    promise = tx.analyze("""
        match { $x isa user; } or { $x isa company; };
        fetch { "email": [$x.email] };
    """)

    # 2. Resolve the promise if you want to access the result or receive an error as an exception
    analyzed = promise.resolve()
```

## [](#_the_response)The response

The response is a type-annotated representation of the query. The following sections cover the essence, but details are left to the [driver reference](#_references).

As one would expect, the response contains each part of a TypeQL query: A (possibly empty) set of preamble functions, the query-pipeline, and a fetch clause, if present.

```python
class AnalyzedQuery:
    def pipeline(self) -> Pipeline
    def preamble(self) -> Iterator[Function]
    def fetch(self) -> Optional[Fetch]
```

### [](#_pipelines_conjunctions)Pipelines & conjunctions

TypeQL [pipelines](../../../typeql-reference/data-model/index.md#_stages) are made up of a sequence of stages, some of which may contain conjunctions.

```python
class Pipeline:
    def stages(self) -> Iterator[PipelineStage]
    def conjunction(self, conjunction_id: ConjunctionID) -> Optional[Conjunction]
    # ...
```

The `PipelineStage` instances returned by the `stages()` method follows the familiar pattern of having an abstract base class which is a union of all the variants. it must be downcast to the appropriate variant using the `is_<variant>` and `as_<variant>` methods.

```python
class PipelineStage(ABC):
    def is_match(self) -> bool
    def as_match(self) -> MatchStage
    # is_insert, as_insert, is_select, as_select, ...

class MatchStage(PipelineStage):
    def block(self) -> ConjunctionID

class SelectStage(PipelineStage):
    def variables(self) -> Iterator[Variable]
```

Stages such as `Match` and `Insert` hold a `ConjunctionID`. This is an indirection which can be used to retrieve the actual conjunction using the `Pipeline.conjunction` method.

```python
#!test[]
#{{
from typedb.driver import *

DB_NAME = "my_database"
address = "localhost:1729"
credentials = Credentials("admin", "password")
options = DriverOptions(DriverTlsConfig.disabled())

driver = TypeDB.driver(address, credentials, options)

with driver.transaction(DB_NAME, TransactionType.READ) as tx:
    # 1. Send the analyze request
    promise = tx.analyze("""
        match { $x isa user; } or { $x isa company; };
        fetch { "email": [$x.email] };
    """)

    # 2. Resolve the promise if you want to access the result or receive an error as an exception
    analyzed = promise.resolve()


#}}
pipeline = analyzed.pipeline()
stages = list(pipeline.stages())
block_id = stages[0].as_match().block()
root_conjunction = pipeline.conjunction(block_id)
```

From the returned conjunction one can access the constraints, as well as the types inferred for the variables in those constraints.

```python
class Conjunction:
    def constraints(self) -> Iterator[Constraint]
    def annotated_variables(self) -> Iterator[Variable]
    def variable_annotations(self, variable: Variable) -> Optional[VariableAnnotations]
```

`VariableAnnotations` refer to the types the variable is annotated with by type-inference. These are not to be confused with [schema-annotations](../../../typeql-reference/annotations/index.md)

### [](#_constraints)Constraints

Similar to stages, the `Constraint` instances returned by the `constraints()` method must be down-cast to the appropriate variant using the `is/as` methods. Sub-patterns such as `or`, `not`, and `try` are also constraints. These hold the `ConjunctionID`(s) of the nested conjunctions.

```python
class Constraint(ABC):
    def is_isa(self) -> bool
    def as_isa(self) -> Isa
    # is_has, as_has, ...

    def is_or(self) -> bool
    def as_or(self) -> Or
    # is_not, as_not, is_try, as_try

class Isa(Constraint):
    # <instance> isa(!) <type>
    def instance(self) -> ConstraintVertex
    def type(self) -> ConstraintVertex
    def exactness(self) -> ConstraintExactness # isa or isa!

# Has, ...

class Or(Constraint):
    def branches(self) -> Iterator[ConjunctionID]
# Not, Try
```

To get the `isa` constraint from the first branch:

```python
#!test[]
#{{
from typedb.driver import *

DB_NAME = "my_database"
address = "localhost:1729"
credentials = Credentials("admin", "password")
options = DriverOptions(DriverTlsConfig.disabled())

driver = TypeDB.driver(address, credentials, options)

with driver.transaction(DB_NAME, TransactionType.READ) as tx:
    # 1. Send the analyze request
    promise = tx.analyze("""
        match { $x isa user; } or { $x isa company; };
        fetch { "email": [$x.email] };
    """)

    # 2. Resolve the promise if you want to access the result or receive an error as an exception
    analyzed = promise.resolve()

pipeline = analyzed.pipeline()
stages = list(pipeline.stages())
block_id = stages[0].as_match().block()
root_conjunction = pipeline.conjunction(block_id)


#}}
constraints = list(root_conjunction.constraints())
or_constraint = constraints[0]
assert or_constraint.is_or()
branches = [pipeline.conjunction(id) for id in or_constraint.as_or().branches()]
first_branch_constraints = list(branches[0].constraints())
first_branch_isa = first_branch_constraints[0]
assert first_branch_isa.is_isa()
```

#### [](#_constraint_vertices)Constraint vertices

Although constraints typically apply on variables, certain TypeQL constraints allow you to directly specify a type-label or a value. Additionally, a `NamedRole` vertex type exists to handle the ambiguity of unscoped role-labels. A `ConstraintVertex` is the union of these four.

The term `vertex` comes from viewing a query as a constraint graph.

A `ConstraintVertex` can be converted to the appropriate variant using the `is/as` methods.

*   A **label** vertex holds the resolved type.
    
*   A **value** vertex holds the value concept of the appropriate value-type.
    
*   A **named-role** vertex holds the internal variable and the unscoped name. The internal variable can be used to retrieve the resolved role-type(s) using the annotations in the conjunction.
    
*   A **variable** vertex holds a variable, which can be used in many places.
    

A variable is shared across constraints. The name of a variable (if it has one) can be read from the pipeline.

```python
#!test[]
#{{
from typedb.driver import *

DB_NAME = "my_database"
address = "localhost:1729"
credentials = Credentials("admin", "password")
options = DriverOptions(DriverTlsConfig.disabled())

driver = TypeDB.driver(address, credentials, options)

with driver.transaction(DB_NAME, TransactionType.READ) as tx:
    # 1. Send the analyze request
    promise = tx.analyze("""
        match { $x isa user; } or { $x isa company; };
        fetch { "email": [$x.email] };
    """)

    # 2. Resolve the promise if you want to access the result or receive an error as an exception
    analyzed = promise.resolve()

pipeline = analyzed.pipeline()
stages = list(pipeline.stages())
block_id = stages[0].as_match().block()
root_conjunction = pipeline.conjunction(block_id)

constraints = list(root_conjunction.constraints())
or_constraint = constraints[0]
assert or_constraint.is_or()
branches = [pipeline.conjunction(id) for id in or_constraint.as_or().branches()]
first_branch_constraints = list(branches[0].constraints())
first_branch_isa = first_branch_constraints[0]
assert first_branch_isa.is_isa()


#}}
var_x = first_branch_isa.instance()
assert var_x.is_variable() and pipeline.get_variable_name(var_x.as_variable()) == "x"
```

If the variable is an output of the pipeline, the name can be used to read answers from query responses. The possible types of a variable in a conjunction can be read from the annotations in the conjunction.

`Variable` and `ConjunctionID` are scoped to a pipeline. Trying to resolve either of these using a pipeline other than the one it originated from (e.g. a pipeline of a preamble function) is undefined behaviour.

### [](#_annotations)Annotations

Type-checking is a central feature of TypeDB. Analyze returns the final set of inferred types for every variable in a conjunction.

```python
#!test[]
#{{
from typedb.driver import *

DB_NAME = "my_database"
address = "localhost:1729"
credentials = Credentials("admin", "password")
options = DriverOptions(DriverTlsConfig.disabled())

driver = TypeDB.driver(address, credentials, options)

with driver.transaction(DB_NAME, TransactionType.READ) as tx:
    # 1. Send the analyze request
    promise = tx.analyze("""
        match { $x isa user; } or { $x isa company; };
        fetch { "email": [$x.email] };
    """)

    # 2. Resolve the promise if you want to access the result or receive an error as an exception
    analyzed = promise.resolve()

pipeline = analyzed.pipeline()
stages = list(pipeline.stages())
block_id = stages[0].as_match().block()
root_conjunction = pipeline.conjunction(block_id)

constraints = list(root_conjunction.constraints())
or_constraint = constraints[0]
assert or_constraint.is_or()
branches = [pipeline.conjunction(id) for id in or_constraint.as_or().branches()]
first_branch_constraints = list(branches[0].constraints())
first_branch_isa = first_branch_constraints[0]
assert first_branch_isa.is_isa()


#}}
var_x = first_branch_isa.instance().as_variable()
assert var_x in list(root_conjunction.annotated_variables())

x_annotations_in_root = root_conjunction.variable_annotations(var_x)
assert x_annotations_in_root.is_instance()

x_types_in_root = list(x_annotations_in_root.as_instance())
labels = set(map(lambda t: t.get_label(), x_types_in_root))
assert labels == {"user", "company"}
```

We return annotations per conjunction because a variable may have different types in different conjunctions.

```typeql
# user in the left branch, company in the right, Either of them at the root.
match { $p isa user; } or { $p isa company; };
```

or

```typeql
match $p has email $email;  # $p is any type that owns email
match $p has name $name;    # The type of $p must also own name
```

### [](#_functions)Functions

A `Function` is a pipeline with a set of arguments, and returns.

```python
class Function:
    def body(self) -> Pipeline

    def argument_variables(self) -> Iterator[Variable]
    def argument_annotations(self) -> Iterator[VariableAnnotations]

    def return_operation(self) -> ReturnOperation
    def return_annotations(self) -> Iterator[VariableAnnotations]
```

### [](#_fetch)Fetch

An analyzed `Fetch` is one of a Dictionary, a List, or a collection of values.

```python
class Fetch(ABC):
    # is/as methods

class FetchObject(Fetch):
    def keys(self) -> Iterator[str]
    def get(self, key: str) -> Fetch

class FetchList(Fetch):
    def element(self) -> Fetch

class FetchLeaf(Fetch):
    def annotations(self) -> Iterator[str]
```

The JSON-like structure of the analyzed `Fetch` reflects that of the `Fetch` stage itself, with leaves being annotated with the value types.

```typeql
match $u isa user;
fetch {
    "email": [ $u.email ],
};
```

To inspect the value-type of the emails:

```python
#!test[]
#{{
from typedb.driver import *

DB_NAME = "my_database"
address = "localhost:1729"
credentials = Credentials("admin", "password")
options = DriverOptions(DriverTlsConfig.disabled())

driver = TypeDB.driver(address, credentials, options)

with driver.transaction(DB_NAME, TransactionType.READ) as tx:
    # 1. Send the analyze request
    promise = tx.analyze("""
        match { $x isa user; } or { $x isa company; };
        fetch { "email": [$x.email] };
    """)

    # 2. Resolve the promise if you want to access the result or receive an error as an exception
    analyzed = promise.resolve()


#}}
root_object = analyzed.fetch().as_object()
email_field = root_object.get("email")
email_list_element = email_field.as_list().element()
email_value_types = list(email_list_element.annotations())
assert email_value_types == ["string"]
```

## [](#_references)References

*   Analyze reference: [rust](../../../reference/typedb-grpc-drivers/rust/index.md#_Analyze), [java](../../../reference/typedb-grpc-drivers/java/index.md#_Analyze), [python](../../../reference/typedb-grpc-drivers/python/index.md#_Analyze)
    

[Queries](../queries/index.md) [Best Practices](../best-practices/index.md)

[Edit on GitHub](https://github.com/typedb/typedb-docs/edit/3.x-development/core-concepts/modules/ROOT/pages/drivers/analyze.adoc) Edit this page on GitHub.