# Query interface and Native Queries

{% hint style="success" %}
This is the official documentation of the `forestadmin-agent-django` and `forestadmin-agent-flask` Python agents.
{% endhint %}

Forest Admin can connect to any data source, as long as it can be represented as a collection of records that have a common structure.

To achieve that, Forest Admin needs to abstract away data source differences: each connector "speaks" the language of a given API on one side and exposes the Forest Admin Query Interface on the other.

This interface is called the `Forest Admin Query Interface`, it is *not* a full-featured ORM: its objective is to be "just enough" to fuel Forest Admin.

## Choosing how to query your data

The Forest Admin Query Interface is used to implement all native features of the admin panel, however, when writing custom code ([creating new actions](https://docs.forestadmin.com/developer-guide-agents-python/agent-customization/actions), [fields](https://docs.forestadmin.com/developer-guide-agents-python/agent-customization/fields), ...), you can either access your data using the Forest Admin Query Interface or using the native driver.

The choice is yours, and you will probably use both depending on the situation.

| -                                                                  | Forest Admin Query Interface                         | Native Driver                             |
| ------------------------------------------------------------------ | ---------------------------------------------------- | ----------------------------------------- |
| Code consistency                                                   | 👍 Use the same query interface for all data sources | 👎 Different API for each database / SaaS |
| Customizations can be queried (computed field, relationships, ...) | 👍 Yes                                               | 👎 No                                     |
| Features                                                           | 👎 Common denominator is exposed                     | 👍 All features of the underlying API     |
| In-app deployments                                                 | 👎 Difficult to reuse your existing code             | 👍 Re-use your existing code              |
| Learning curve                                                     | 👎 The interface is Forest Admin specific            | 👍 You already know how to write SQL      |
| Native support for filters from the UI                             | 👍 Yes                                               | 👎 No                                     |
| Total                                                              | 3 x 👍 + 3 x 👎                                      | 3 x 👍 + 3 x 👎                           |

## In practice

### Querying with the native driver

As the name implies, native drivers have different interfaces for each data source.

{% tabs %}
{% tab title="SQLAlchemy" %}

```python
from forestadmin.datasource_toolkit.context.collection_context import CollectionCustomizationContext

def high_price_segment(context: CollectionCustomizationContext):
    query = """
        SELECT p.id as product_id, count(o.id) as nb
        FROM Order o
        INNER JOIN Product p ON o.product = p.id
        GROUP BY p.id
        ORDER BY nb DESC';
    """

    with context.collection.get_native_driver() as driver:
        cursor = driver.execute(query).all()
        rows = [*cursor]

    return ConditionTreeLeaf(
        field="id",
        operator="in",
        value=[r[0] for r in rows],
    )

agent.customize_collection("order").add_segment("highPrice", high_price_segment)
```

{% endtab %}

{% tab title="Django" %}

```python
from sqlalchemy.sql import text
from forestadmin.datasource_toolkit.context.collection_context import CollectionCustomizationContext

def high_price_segment(context: CollectionCustomizationContext):
    query = """
        SELECT p.id as product_id, count(o.id) as nb
        FROM Order o
        INNER JOIN Product p ON o.product = p.id
        GROUP BY p.id
        ORDER BY nb DESC';
    """

    with context.collection.get_native_driver() as driver:
        cursor = driver.execute(query)
        rows = [*cursor]

    return ConditionTreeLeaf(
        field="id",
        operator="in",
        value=[r[0] for r in rows],
    )

agent.customize_collection("order").add_segment("highPrice", high_price_segment)
```

{% endtab %}
{% endtabs %}

### Querying with the Forest Admin Query Interface

Queries can be executed directly, by calling the methods exposed by `context.datasource` and `context.collection`.

```python
from sqlalchemy.sql import text
from forestadmin.datasource_toolkit.context.collection_context import CollectionCustomizationContext
from forestadmin.datasource_toolkit.interfaces.query.aggregation import Aggregation
from forestadmin.datasource_toolkit.interfaces.query.filter.unpaginated import Filter
from forestadmin.datasource_toolkit.interfaces.query.condition_tree.nodes.leaf import ConditionTreeLeaf

async def my_segment_function(context: CollectionCustomizationContext):
    rows = await context.collection.datasource.get_collection("Order").aggregate(
        context.caller,
        Filter({}),
        Aggregation({
            "field":"id",
            "operation": "Count",
            "groups": [{"field": "category_id"}],
        })
    )
    return ConditionTreeLeaf(
        field="id",
        operator="in",
        value=[r["id"] for r in rows],
    )

agent.customize_collection("order").add_segment("mySegment", my_segment_function)
```

<details>

<summary>Data Source Interface</summary>

```python
class Datasource(Generic[BoundCollection], abc.ABC):
    @abc.abstractproperty
    def collections(self) -> List[BoundCollection]:
        raise NotImplementedError

    @abc.abstractmethod
    def get_collection(self, name: str) -> BoundCollection:
        raise NotImplementedError

    @abc.abstractmethod
    def add_collection(self, collection: BoundCollection) -> None:
        raise NotImplementedError

```

</details>

<details>

<summary>Collection Interface</summary>

Parameters are explained in depth on the following pages:

* [Fields and projections](https://docs.forestadmin.com/developer-guide-agents-python/data-sources/getting-started/queries/fields-projections)
* [Filters](https://docs.forestadmin.com/developer-guide-agents-python/data-sources/getting-started/queries/filters)
* [Aggregations](https://docs.forestadmin.com/developer-guide-agents-python/data-sources/getting-started/queries/aggregations)

```python
class Collection(CollectionModel, abc.ABC):
    @abc.abstractmethod
    def get_native_driver(self):
        """return native driver"""

    @abc.abstractmethod
    async def execute(
        self,
        caller: User,
        name: str,
        data: RecordsDataAlias,
        filter_: Optional[Filter],
    ) -> ActionResult:
        """to execute an action"""
        raise ForestException(f"Action {name} is not implemented")

    @abc.abstractmethod
    async def get_form(
        self,
        caller: User,
        name: str,
        data: Optional[RecordsDataAlias],
        filter_: Optional[Filter],
        meta: Optional[Dict[str, Any]],
    ) -> List[ActionField]:
        """to get the form of an action"""
        return []

    @abc.abstractmethod
    async def render_chart(self, caller: User, name: str, record_id: List) -> Chart:
        """to render a chart"""
        raise ForestException(f"Chart {name} is not implemented")

    @abc.abstractmethod
    async def create(
        self, caller: User, data: List[RecordsDataAlias]
    ) -> List[RecordsDataAlias]:
        """to create records"""

    @abc.abstractmethod
    async def list(
        self, caller: User, filter_: PaginatedFilter, projection: Projection
    ) -> List[RecordsDataAlias]:
        """to list records"""

    @abc.abstractmethod
    async def update(
        self, caller: User, filter_: Optional[Filter], patch: RecordsDataAlias
    ) -> None:
        """to update records"""

    @abc.abstractmethod
    async def delete(self, caller: User, filter_: Optional[Filter]) -> None:
        """to delete records"""

    @abc.abstractmethod
    async def aggregate(
        self,
        caller: User,
        filter_: Optional[Filter],
        aggregation: Aggregation,
        limit: Optional[int] = None
    ) -> List[AggregateResult]:
        """to make aggregate request"""

```

</details>
