# SQLAlchemy Support

Since most people prefer to use an ORM the crate client library provides support for SQLAlchemy out of the box.

The Dialect is installed and registered by default (e.g. if crate was installed using pip) and can be used without further configuration.

## Connection String

In SQLAlchemy a connection is established using the create_engine function. This function takes a connection string that varies from database to database.

In order to connect to a crate cluster the following connection strings are valid:

    >>> sa.create_engine('crate://')
    Engine(crate://)

This will connect to the default server (‘127.0.0.1:9200’). In order to connect to a different server the following syntax can be used:

    >>> sa.create_engine('crate://otherserver:9200')
    Engine(crate://otherserver:9200)

Since Crate is a clustered database that usually consists of multiple server it is recommended to connect to all of them. This enables the DB-API layer to use round-robin to distribute the load and skip a server if it becomes unavailable.

The connect_args parameter has to be used to do so:

    >>> sa.create_engine('crate://', connect_args={
    ...     'servers': ['host1:9200', 'host2:9200']
    ... })
    Engine(crate://)

### Supported Operations and Limitations

Currently Crate only implements a subset of the SQL standard. Therefore many operations that SQLAlchemy provides are currently not supported.

An overview of supported operations:

    - simple select statements including filter operations, limit and offset. But
      without joins or subselects. Order by is also supported.
    - Inserting new rows.

Unlike other databases that are usually used with SQLAlchemy, Crate isn’t a RDBMS but instead is document oriented with eventual consistency.

This means that there is no transaction support and the database should be modeled without relationships.

In SQLAlchemy a Session object is used to query the database. This Session object contains a rollback method that in Crate won’t do anything at all. Similar the commit method will only flush but not actually commit, as there is no commit in crate.

Please refer to the SQLAlchemy documentation for more information about the session management and the concept of flush.

### Complex Types

In a document oriented database it is a common pattern to store complex objects within a single field. For such cases the crate package provides the Craty type.

Below is a schema definition using SQLAlchemy’s declarative approach:

    >>> from crate.client.sqlalchemy.types import Craty
    >>> from uuid import uuid4

    >>> def gen_key():
    ...     return str(uuid4())

    >>> class Character(Base):
    ...     __tablename__ = 'characters'
    ...     id = sa.Column(sa.String, primary_key=True, default=gen_key)
    ...     name = sa.Column(sa.String)
    ...     details = sa.Column(Craty)

Using the Session two characters are added that have additional attributes inside the details column that weren’t defined in the schema:

    >>> arthur = Character(name='Arthur Dent')
    >>> arthur.details = {}
    >>> arthur.details['gender'] = 'male'
    >>> arthur.details['species'] = 'human'
    >>> session.add(arthur)

    >>> trillian = Character(name='Tricia McMillan')
    >>> trillian.details = {}
    >>> trillian.details['gender'] = 'female'
    >>> trillian.details['species'] = 'human'
    >>> trillian.details['female_only_attribute'] = 1
    >>> session.add(trillian)
    >>> session.commit()

After INSERT statements are sent to the database the newly inserted rows aren’t immediately available for search because the index is only updated periodically:

    >>> from time import sleep
    >>> sleep(2)

Note

Newly inserted rows can still be queried immediately if a lookup by the primary key is done.

A regular select query will then fetch the whole documents:

    >>> query = session.query(Character).order_by(Character.name)
    >>> [(c.name, c.details['gender']) for c in query]
    [('Arthur Dent', 'male'), ('Tricia McMillan', 'female')]

But it is also possible to just select a part of the document, even inside the Craty type:

    >>> sorted(session.query(Character.details['gender']).all())
    [('female',), ('male',)]

In addition, filtering on the attributes inside the details column is also possible:

    >>> query = session.query(Character.name)
    >>> query.filter(Character.details['gender'] == 'male').all()
    [('Arthur Dent',)]