docs: def search_datasets(dataset_id=None,
                  dataset_name=None,
                  collection_name=None,
                  data_type=None,
                  unit_id=None,
                  scenario_id=None,
                  metadata_key=None,
                  metadata_val=None,
                  attr_id = None,
                  type_id = None,
                  unconnected = None,
                  inc_metadata='N',
                  inc_val = 'N',
                  page_start = 0,
                  page_size   = 2000,
                  **kwargs):
      """
          Get multiple datasets, based on several
          filters. If all filters are set to None, all
          datasets in the DB (that the user is allowe to see)
          will be returned.
      """
  
  
      log.info("Searching datasets: \ndatset_id: %s,\n"
                                    "datset_name: %s,\n"
                                    "collection_name: %s,\n"
                                    "data_type: %s,\n"
                                    "unit_id: %s,\n"
                                    "scenario_id: %s,\n"
                                    "metadata_key: %s,\n"
                                    "metadata_val: %s,\n"
                                    "attr_id: %s,\n"
                                    "type_id: %s,\n"
                                    "unconnected: %s,\n"
                                    "inc_metadata: %s,\n"
                                    "inc_val: %s,\n"
                                    "page_start: %s,\n"
                                    "page_size: %s" % (dataset_id,
                  dataset_name,
                  collection_name,
                  data_type,
                  unit_id,
                  scenario_id,
                  metadata_key,
                  metadata_val,
                  attr_id,
                  type_id,
                  unconnected,
                  inc_metadata,
                  inc_val,
                  page_start,
                  page_size))
  
      if page_size is None:
          page_size = config.get('SEARCH', 'page_size', 2000)
  
      user_id = int(kwargs.get('user_id'))
  
      dataset_qry = db.DBSession.query(Dataset.id,
              Dataset.type,
              Dataset.unit_id,
              Dataset.name,
              Dataset.hidden,
              Dataset.cr_date,
              Dataset.created_by,
              DatasetOwner.user_id,
              null().label('metadata'),
              Dataset.value
      )
  
      #Dataset ID is unique, so there's no point using the other filters.
      #Only use other filters if the datset ID is not specified.
      if dataset_id is not None:
          dataset_qry = dataset_qry.filter(
              Dataset.id==dataset_id)
  
      else:
          if dataset_name is not None:
              dataset_qry = dataset_qry.filter(
                  func.lower(Dataset.name).like("%%%s%%"%dataset_name.lower())
              )
          if collection_name is not None:
              dc = aliased(DatasetCollection)
              dci = aliased(DatasetCollectionItem)
              dataset_qry = dataset_qry.join(dc,
                          func.lower(dc.name).like("%%%s%%"%collection_name.lower())
                          ).join(dci,and_(
                              dci.collection_id == dc.id,
                              dci.dataset_id == Dataset.id))
  
          if data_type is not None:
              dataset_qry = dataset_qry.filter(
                  func.lower(Dataset.type) == data_type.lower())
  
          #null is a valid unit, so we need a way for the searcher
          #to specify that they want to search for datasets with a null unit
          #rather than ignoring the unit. We use 'null' to do this.
  
          if unit_id is not None:
              dataset_qry = dataset_qry.filter(
                  Dataset.unit_id == unit_id)
  
          if scenario_id is not None:
              dataset_qry = dataset_qry.join(ResourceScenario,
                                  and_(ResourceScenario.dataset_id == Dataset.id,
                                  ResourceScenario.scenario_id == scenario_id))
  
          if attr_id is not None:
              dataset_qry = dataset_qry.join(
                  ResourceScenario, ResourceScenario.dataset_id == Dataset.id).join(
                  ResourceAttr, and_(ResourceAttr.id==ResourceScenario.resource_attr_id,
                                    ResourceAttr.attr_id==attr_id))
  
          if type_id is not None:
              dataset_qry = dataset_qry.join(
                  ResourceScenario, ResourceScenario.dataset_id == Dataset.id).join(
                  ResourceAttr, ResourceAttr.id==ResourceScenario.resource_attr_id).join(
                  TypeAttr, and_(TypeAttr.attr_id==ResourceAttr.attr_id, TypeAttr.type_id==type_id))
  
          if unconnected == 'Y':
              stmt = db.DBSession.query(distinct(ResourceScenario.dataset_id).label('dataset_id'),
                                  literal_column("0").label('col')).subquery()
              dataset_qry = dataset_qry.outerjoin(
                  stmt, stmt.c.dataset_id == Dataset.id)
              dataset_qry = dataset_qry.filter(stmt.c.col == None)
          elif unconnected == 'N':
              #The dataset has to be connected to something
              stmt = db.DBSession.query(distinct(ResourceScenario.dataset_id).label('dataset_id'),
                                  literal_column("0").label('col')).subquery()
              dataset_qry = dataset_qry.join(
                  stmt, stmt.c.dataset_id == Dataset.id)
          if metadata_key is not None and metadata_val is not None:
              dataset_qry = dataset_qry.join(Metadata,
                                  and_(Metadata.dataset_id == Dataset.id,
                                  func.lower(Metadata.key).like("%%%s%%"%metadata_key.lower()),
                                  func.lower(Metadata.value).like("%%%s%%"%metadata_val.lower())))
          elif metadata_key is not None and metadata_val is None:
              dataset_qry = dataset_qry.join(Metadata,
                                  and_(Metadata.dataset_id == Dataset.id,
                                  func.lower(Metadata.key).like("%%%s%%"%metadata_key.lower())))
          elif metadata_key is None and metadata_val is not None:
              dataset_qry = dataset_qry.join(Metadata,
                                  and_(Metadata.dataset_id == Dataset.id,
                                  func.lower(Metadata.value).like("%%%s%%"%metadata_val.lower())))
  
      #All datasets must be joined on dataset owner so only datasets that the
      #user can see are retrieved.
      dataset_qry = dataset_qry.outerjoin(DatasetOwner,
                                  and_(DatasetOwner.dataset_id==Dataset.id,
                                  DatasetOwner.user_id==user_id))
  
      dataset_qry = dataset_qry.filter(or_(Dataset.hidden=='N', and_(DatasetOwner.user_id is not None, Dataset.hidden=='Y')))
  
      log.info(str(dataset_qry))
  
      datasets = dataset_qry.all()
  
      log.info("Retrieved %s datasets", len(datasets))
  
      #page the datasets:
      if page_start + page_size > len(datasets):
          page_end = None
      else:
          page_end = page_start + page_size
  
      datasets = datasets[page_start:page_end]
  
      log.info("Datasets paged from result %s to %s", page_start, page_end)
  
      datasets_to_return = []
      for dataset_row in datasets:
  
          dataset_dict = dataset_row._asdict()
  
  
  
          if inc_val == 'N':
              dataset_dict['value'] = None
          else:
              #convert the value row into a string as it is returned as a binary
              if dataset_row.value is not None:
                  dataset_dict['value'] = str(dataset_row.value)
  
          if inc_metadata=='Y':
              metadata = db.DBSession.query(Metadata).filter(Metadata.dataset_id==dataset_row.dataset_id).all()
              dataset_dict['metadata'] = metadata
          else:
              dataset_dict['metadata'] = []
  
          dataset = namedtuple('Dataset', dataset_dict.keys())(**dataset_dict)
  
          datasets_to_return.append(dataset)
  
      return datasets_to_return