def filter(self, cls_or_collection, query, raw = False,only = None,include = None):
        """
        Filter objects from the database that correspond to a given set of properties.

        See :py:meth:`blitzdb.backends.base.Backend.filter` for documentation of individual parameters

        .. note::

            This function supports all query operators that are available in SQLAlchemy and returns a query set
            that is based on a SQLAlchemy cursor.

        """

        if not isinstance(cls_or_collection, six.string_types):
            collection = self.get_collection_for_cls(cls_or_collection)
            cls = cls_or_collection
        else:
            collection = cls_or_collection
            cls = self.get_cls_for_collection(collection)

        table = self._collection_tables[collection]

        joins = defaultdict(dict)
        joins_list = []
        group_bys = []
        havings = []

        def compile_query(collection,query,table = None,path = None):
            if path is None:
                path = []

            """
            This function emits a list of WHERE statements that can be used to retrieve
            """

            if table is None:
                table = self._collection_tables[collection]

            where_statements  = []

            if any([True if key.startswith('$') else False for key in query.keys()]):
                #this is a special operator query
                if len(query) > 1:
                    raise AttributeError('Currently not supported!')
                operator = list(query.keys())[0][1:]
                if not operator in ('and','or','not'):
                    raise AttributeError("Non-supported logical operator: $%s" % operator)
                if operator in ('and','or'):
                    where_statements = [sq for expr in query['$%s' % operator]
                                        for sq in compile_query(collection,expr,path = path)]
                    if operator == 'and':
                        return [and_(*where_statements)]
                    else:
                        return [or_(*where_statements)]
                elif operator  == 'not':
                    return [not_(*compile_query(collection,query['$not'],table = table,path = path))]

            def compile_one_to_many_query(key,query,field_name,related_table,count_column,path):

                def prepare_subquery(tail,query_dict):
                    d = {}
                    if not tail:
                        if isinstance(query_dict,dict):
                            return query_dict.copy()
                        if not isinstance(query_dict,Document):
                            raise AttributeError("Must be a document!")
                        if not query_dict.pk:
                            raise AttributeError("Performing a query without a primary key!")
                        return {'pk' : query_dict.pk}
                    return {tail : query_dict}

                tail = key[len(field_name)+1:]

                if isinstance(query,Document) and not tail:
                    query = {'pk' : query.pk}

                #to do: implement $size and $not: {$size} operators...
                if isinstance(query,dict) and len(query) == 1 and list(query.keys())[0] in ('$all','$in','$elemMatch','$nin'):
                    #this is an $in/$all/$nin query
                    query_type = list(query.keys())[0][1:]
                    subquery = list(query.values())[0]

                    if query_type == 'elemMatch':
                        queries = compile_query(params['collection'],
                                                prepare_subquery(tail,query['$elemMatch']),
                                                table = related_table,
                                                path = path)
                        return queries
                    else:
                        if isinstance(subquery,(ManyToManyProxy,QuerySet)):
                            if tail:
                                #this query has a tail
                                query = {tail : query}
                                queries = compile_query(params['collection'],query,
                                                table = related_table,
                                                path = path)
                                return queries
                            #this is a query with a ManyToManyProxy/QuerySet
                            if isinstance(subquery,ManyToManyProxy):
                                qs = subquery.get_queryset()
                            else:
                                qs = subquery
                            if not query_type in ('in','nin','all'):
                                raise AttributeError
                            if query_type == 'all':
                                op = 'in'
                            else:
                                op = query_type
                            if query_type == 'all':
                                cnt = func.count(count_column)
                                condition = cnt == qs.get_count_select()
                                havings.append(condition)
                            return [getattr(related_table.c['pk'],op+'_')(qs.get_select(columns = ['pk']))]
                        elif isinstance(subquery,(list,tuple)):
                            if subquery and isinstance(subquery[0],dict) and len(subquery[0]) == 1 and \
                            list(subquery[0].keys())[0] == '$elemMatch':
                                queries = [sq for v in subquery for sq in compile_query(params['collection'],
                                                                                        prepare_subquery(tail,v['$elemMatch']),
                                                                                        table = related_table,
                                                                                        path = path)]
                            else:
                                queries = [sq for v in subquery for sq in compile_query(params['collection'],
                                                                                        prepare_subquery(tail,v),
                                                                                        table = related_table,
                                                                                        path = path)]
                            where_statement = or_(*queries)

                            if query_type == 'nin':
                                where_statement = not_(where_statement)

                            if query_type == 'all' and len(queries) > 1:
                                cnt = func.count(count_column)
                                havings.append(cnt == len(queries))

                            return [where_statement]
                        else:
                            raise AttributeError("$in/$nin/$all query requires a list/tuple/QuerySet/ManyToManyProxy")
                else:
                    return compile_query(params['collection'],prepare_subquery(tail,query),
                                    table = related_table,
                                    path = path)

            def compile_many_to_many_query(key,value,field_name,params,relationship_table,path):

                related_collection = params['collection']
                related_table = self._collection_tables[related_collection]

                path_str = ".".join(path)

                if path_str in joins[relationship_table]:
                    relationship_table_alias = joins[relationship_table][path_str]
                else:
                    relationship_table_alias = relationship_table.alias()
                    joins[relationship_table][path_str] = relationship_table_alias
                    joins_list.append((relationship_table_alias,
                                       relationship_table_alias.c[params['pk_field_name']] == table.c['pk']))

                if path_str in joins[related_table]:
                    related_table_alias = joins[related_table][path_str]
                else:
                    related_table_alias = related_table.alias()
                    joins[related_table][path_str] = related_table_alias
                    joins_list.append((related_table_alias,relationship_table_alias.c[params['related_pk_field_name']] == related_table_alias.c['pk']))

                return compile_one_to_many_query(key,value,field_name,related_table_alias,relationship_table_alias.c[params['pk_field_name']],new_path)

            def prepare_special_query(field_name,params,query):
                def sanitize(value):
                    if isinstance(value,(list,tuple)):
                        return [v.pk if isinstance(v,Document) else v for v in value]
                    return value
                column_name = params['column']
                if '$not' in query:
                    return [not_(*prepare_special_query(column_name,params,sanitize(query['$not'])))]
                elif '$in' in query:
                    if not query['$in']:
                        #we return an impossible condition since the $in query does not contain any values
                        return [expression.cast(True,Boolean) == expression.cast(False,Boolean)]
                    return [table.c[column_name].in_(sanitize(query['$in']))]
                elif '$nin' in query:
                    if not query['$nin']:
                        return [expression.cast(True,Boolean) == expression.cast(False,Boolean)]
                    return [~table.c[column_name].in_(sanitize(query['$nin']))]
                elif '$eq' in query:
                    return [table.c[column_name] == sanitize(query['$eq'])]
                elif '$ne' in query:
                    return [table.c[column_name] != sanitize(query['$ne'])]
                elif '$gt' in query:
                    return [table.c[column_name] > sanitize(query['$gt'])]
                elif '$gte' in query:
                    return [table.c[column_name] >= sanitize(query['$gte'])]
                elif '$lt' in query:
                    return [table.c[column_name] < sanitize(query['$lt'])]
                elif '$lte' in query:
                    return [table.c[column_name] <= sanitize(query['$lte'])]
                elif '$exists' in query:
                    if query['$exists']:
                        return [table.c[column_name] != None]
                    else:
                        return [table.c[column_name] == None]
                elif '$like' in query:
                    return [table.c[column_name].like(expression.cast(query['$like'],String))]
                elif '$ilike' in query:
                    return [table.c[column_name].ilike(expression.cast(query['$ilike'],String))]
                elif '$regex' in query:
                    if not self.engine.url.drivername in ('postgres','mysql','sqlite'):
                        raise AttributeError("Regex queries not supported with %s engine!" % self.engine.url.drivername)
                    return [table.c[column_name].op('REGEXP')(expression.cast(query['$regex'],String))]
                else:
                    raise AttributeError("Invalid query!")

            #this is a normal, field-base query
            for key,value in query.items():
                for field_name,params in self._index_fields[collection].items():
                    if key == field_name:
                        if isinstance(value,re._pattern_type):
                            value = {'$regex' : value.pattern}
                        if isinstance(value,dict):
                            #this is a special query
                            where_statements.extend(prepare_special_query(field_name,params,value))
                        else:
                            #this is a normal value query
                            where_statements.append(table.c[params['column']] == expression.cast(value,params['type']))
                        break
                else:
                    #we check the normal relationships
                    for field_name,params in self._related_fields[collection].items():
                        if key.startswith(field_name):

                            head,tail = key[:len(field_name)],key[len(field_name)+1:]
                            new_path = path + [head]
                            path_str = ".".join(new_path)
                            #ManyToManyField
                            if isinstance(params['field'],ManyToManyField):
                                relationship_table = self._relationship_tables[collection][field_name]
                                where_statements.extend(compile_many_to_many_query(key,value,field_name,params,relationship_table,path = new_path))
                            elif isinstance(params['field'],ForeignKeyField):#this is a normal ForeignKey relation
                                if key == field_name:
                                    #this is a ForeignKey query
                                    if isinstance(value,dict):
                                        if len(value) == 1:
                                            key,query = list(value.items())[0]
                                            if key == '$exists':
                                                if not isinstance(query,bool):
                                                    raise AttributeError("$exists operator requires a Boolean operator")
                                                if query:
                                                    where_statements.append(table.c[params['column']] != None)
                                                else:
                                                    where_statements.append(table.c[params['column']] == None)
                                                break
                                            elif not key in ('$in','$nin'):
                                                raise AttributeError("Invalid query!")
                                            query_type = key[1:]
                                        else:
                                            raise AttributeError("Invalid query!")
                                    else:
                                        query_type = 'exact'
                                        query = value
                                    if isinstance(query,(QuerySet,ManyToManyProxy)):
                                        if not query_type in ('in','nin'):
                                            raise AttributeError("QuerySet/ManyToManyProxy objects must be used in conjunction with $in/$nin when querying a ForeignKey relationship")
                                        if isinstance(query,ManyToManyProxy):
                                            qs = query.get_queryset()
                                        else:
                                            qs = query
                                        if qs.count is not None and qs.count == 0:
                                            raise AttributeError("$in/$nin query with empty QuerySet/ManyToManyProxy!")
                                        if qs.cls is not params['class']:
                                            raise AttributeError("Invalid QuerySet class!")
                                        condition = getattr(table.c[params['column']],query_type+'_')(qs.get_select(columns = ['pk']))
                                        where_statements.append(condition)
                                    elif isinstance(query,(list,tuple)):
                                        if not query_type in ('in','nin'):
                                            raise AttributeError("Lists/tuples must be used in conjunction with $in/$nin when querying a ForeignKey relationship")
                                        if not query:
                                            raise AttributeError("in/nin query with empty list!")
                                        if query[0].__class__ is params['class']:
                                            if any((element.__class__ is not params['class'] for element in query)):
                                                raise AttributeError("Invalid document type in ForeignKey query")
                                            where_statements.append(getattr(table.c[params['column']],query_type+'_')([expression.cast(doc.pk,params['type']) for doc in query]))
                                        else:
                                            where_statements.append(getattr(table.c[params['column']],query_type+'_')([expression.cast(element,params['type']) for element in query]))
                                    elif isinstance(query,Document):
                                        #we need an exact clas match here...
                                        if query.__class__ is not params['class']:
                                            raise AttributeError("Invalid Document class!")
                                        where_statements.append(table.c[params['column']] == query.pk)
                                    else:
                                        where_statements.append(table.c[params['column']] == expression.cast(query,params['class'].Meta.PkType))
                                else:
                                    #we query a sub-field of the relation
                                    related_table = self._collection_tables[params['collection']]

                                    if path_str in joins[related_table]:
                                        related_table_alias = joins[related_table][path_str]
                                    else:
                                        related_table_alias = related_table.alias()
                                        joins[related_table][path_str] = related_table_alias
                                        joins_list.append((related_table_alias,table.c[params['column']] == related_table_alias.c['pk']))
                                    where_statements.extend(compile_query(params['collection'],{tail : value},table = related_table_alias,path = new_path))
                            elif isinstance(params['field'],OneToManyField):
                                related_table = self._collection_tables[params['collection']]

                                if path_str in joins[related_table]:
                                    related_table_alias = joins[related_table][path_str]
                                else:
                                    related_table_alias = related_table.alias()
                                    joins[related_table][path_str] = related_table_alias
                                    joins_list.append((related_table_alias,related_table_alias.c[params['backref']['column']] == table.c['pk']))

                                where_statements.extend(compile_one_to_many_query(key,value,field_name,related_table_alias,table.c.pk,new_path))
                            break
                    else:
                        raise AttributeError("Query over non-indexed field %s in collection %s!" % (key,collection))
            return where_statements

        compiled_query = compile_query(collection,query)

        if len(compiled_query) > 1:
            compiled_query = and_(*compiled_query)
        elif compiled_query:
            compiled_query = compiled_query[0]
        else:
            compiled_query = None

        return QuerySet(backend = self, table = table,
                        joins = joins_list,
                        cls = cls,
                        condition = compiled_query,
                        raw = raw,
                        group_bys = group_bys,
                        only = only,
                        include = include,
                        havings = havings
                        )
Filter objects from the database that correspond to a given set of properties.

        See :py:meth:`blitzdb.backends.base.Backend.filter` for documentation of individual parameters

        .. note::

            This function supports all query operators that are available in SQLAlchemy and returns a query set
            that is based on a SQLAlchemy cursor.