================
Advanced queries
================

Neomodel contains an API for querying sets of nodes without having to write cypher::

    class SupplierRel(StructuredRel):
        since = DateTimeProperty(default=datetime.now)


    class Supplier(StructuredNode):
        name = StringProperty()
        delivery_cost = IntegerProperty()
        coffees = RelationshipTo('Coffee', 'SUPPLIES')


    class Coffee(StructuredNode):
        name = StringProperty(unique_index=True)
        price = IntegerProperty()
        suppliers = RelationshipFrom(Supplier, 'SUPPLIES', model=SupplierRel)

Node sets and filtering
=======================

The ``.nodes`` property of a class returns all nodes of that type from the database.

This set (or `NodeSet`) can be iterated over and filtered on. Under the hood it uses labels introduced in Neo4J 2::

    # nodes with label Coffee whose price is greater than 2
    Coffee.nodes.filter(price__gt=2)

    try:
        java = Coffee.nodes.get(name='Java')
    except Coffee.DoesNotExist:
        print "Couldn't find coffee 'Java'"

The filter method borrows the same Django filter format with double underscore prefixed operators:

- lt - less than
- gt - greater than
- lte - less than or equal to
- gte - greater than or equal to
- ne - not equal
- in - item in list
- isnull - `True` IS NULL, `False` IS NOT NULL
- exact - string equals
- iexact - string equals, case insensitive
- contains - contains string value
- icontains - contains string value, case insensitive
- startswith - starts with string value
- istartswith - starts with string value, case insensitive
- endswith - ends with string value
- iendswith - ends with string value, case insensitive
- regex - matches a regex expression
- iregex - matches a regex expression, case insensitive

Complex lookups with ``Q`` objects
==================================

Keyword argument queries -- in `filter`,
etc. -- are "AND"ed together. To execute more complex queries (for
example, queries with ``OR`` statements), `Q objects <neomodel.Q>` can 
be used.

A `Q object` (``neomodel.Q``) is an object
used to encapsulate a collection of keyword arguments. These keyword arguments
are specified as in "Field lookups" above.

For example, this ``Q`` object encapsulates a single ``LIKE`` query::

    from neomodel import Q
    Q(name__startswith='Py')

``Q`` objects can be combined using the ``&`` and ``|`` operators. When an
operator is used on two ``Q`` objects, it yields a new ``Q`` object.

For example, this statement yields a single ``Q`` object that represents the
"OR" of two ``"name__startswith"`` queries::

    Q(name__startswith='Py') | Q(name__startswith='Jav')

This is equivalent to the following SQL ``WHERE`` clause::

    WHERE name STARTS WITH 'Py' OR name STARTS WITH 'Jav'

Statements of arbitrary complexity can be composed by combining ``Q`` objects
with the ``&`` and ``|`` operators and use parenthetical grouping. Also, ``Q``
objects can be negated using the ``~`` operator, allowing for combined lookups
that combine both a normal query and a negated (``NOT``) query::

    Q(name__startswith='Py') | ~Q(year=2005)

Each lookup function that takes keyword-arguments
(e.g. `filter`, `exclude`, `get`) can also be passed one or more
``Q`` objects as positional (not-named) arguments. If multiple
``Q`` object arguments are provided to a lookup function, the arguments will be "AND"ed
together. For example::

    Lang.nodes.filter(
        Q(name__startswith='Py'),
        Q(year=2005) | Q(year=2006)
    )

This roughly translates to the following Cypher query::

    MATCH (lang:Lang) WHERE name STARTS WITH 'Py'
        AND (year = 2005 OR year = 2006)
        return lang;

Lookup functions can mix the use of ``Q`` objects and keyword arguments. All
arguments provided to a lookup function (be they keyword arguments or ``Q``
objects) are "AND"ed together. However, if a ``Q`` object is provided, it must
precede the definition of any keyword arguments. For example::

    Lang.nodes.get(
        Q(year=2005) | Q(year=2006),
        name__startswith='Py',
    )

This would be a valid query, equivalent to the previous example;

Has a relationship
==================

The `has` method checks for existence of (one or more) relationships, in this case it returns a set of `Coffee` nodes which have a supplier::

    Coffee.nodes.has(suppliers=True)

This can be negated by setting `suppliers=False`, to find `Coffee` nodes without `suppliers`.

Iteration, slicing and more
===========================

Iteration, slicing and counting is also supported::

    # Iterable
    for coffee in Coffee.nodes:
        print coffee.name

    # Sliceable using python slice syntax
    coffee = Coffee.nodes.filter(price__gt=2)[2:]

The slice syntax returns a NodeSet object which can in turn be chained.

Length and boolean methods dont return NodeSet objects and cannot be chained further::

    # Count with __len__
    print len(Coffee.nodes.filter(price__gt=2))

    if Coffee.nodes:
        print "We have coffee nodes!"

Filtering by relationship properties
====================================

Filtering on relationship properties is also possible using the `match` method. Note that again these relationships must have a definition.::

    coffee_brand = Coffee.nodes.get(name="BestCoffeeEver")

    for supplier in coffee_brand.suppliers.match(since_lt=january):
        print(supplier.name)

Ordering by property
====================

Ordering results by a particular property is done via th `order_by` method::

    # Ascending sort
    for coffee in Coffee.nodes.order_by('price'):
        print(coffee, coffee.price)

    # Descending sort
    for supplier in Supplier.nodes.order_by('-delivery_cost'):
        print(supplier, supplier.delivery_cost)


Removing the ordering from a previously defined query, is done by passing `None` to `order_by`::

    # Sort in descending order
    suppliers = Supplier.nodes.order_by('-delivery_cost')

    # Don't order; yield nodes in the order neo4j returns them
    suppliers = suppliers.order_by(None)

For random ordering simply pass '?' to the order_by method::

    Coffee.nodes.order_by('?')

Retrieving paths
================

You can retrieve a whole path of already instantiated objects corresponding to 
the nodes and relationship classes with a single query.

Suppose the following schema:

::

    class PersonLivesInCity(StructuredRel):
        some_num = IntegerProperty(index=True, 
                                   default=12)
    
    class CountryOfOrigin(StructuredNode):
        code = StringProperty(unique_index=True, 
                              required=True)
    
    class CityOfResidence(StructuredNode):
        name = StringProperty(required=True)
        country = RelationshipTo(CountryOfOrigin, 
                                 'FROM_COUNTRY')
    
    class PersonOfInterest(StructuredNode):
        uid = UniqueIdProperty()
        name = StringProperty(unique_index=True)
        age = IntegerProperty(index=True, 
                              default=0)
    
        country = RelationshipTo(CountryOfOrigin, 
                                 'IS_FROM')
        city = RelationshipTo(CityOfResidence, 
                              'LIVES_IN', 
                              model=PersonLivesInCity)

Then, paths can be retrieved with:

::
   
    q = db.cypher_query("MATCH p=(:CityOfResidence)<-[:LIVES_IN]-(:PersonOfInterest)-[:IS_FROM]->(:CountryOfOrigin) RETURN p LIMIT 1", 
                        resolve_objects = True)

Notice here that ``resolve_objects`` is set to ``True``. This results in ``q`` being a 
list of ``result, result_name`` and ``q[0][0][0]`` being a ``NeomodelPath`` object.

``NeomodelPath`` ``nodes, relationships`` attributes contain already instantiated objects of the 
nodes and relationships in the query, *in order of appearance*.

It would be particularly useful to note here that each object is read exactly once from 
the database. Therefore, nodes will be instantiated to their neomodel node objects and 
relationships to their relationship models *if such a model exists*. In other words, 
relationships with data (such as ``PersonLivesInCity`` above) will be instantiated to their
respective objects or ``StrucuredRel`` otherwise. Relationships do not "reload" their 
end-points (unless this is required).
