docs: 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
                          )