docs: def read(self, symbol, chunk_range=None, filter_data=True, **kwargs):
          """
          Reads data for a given symbol from the database.
  
          Parameters
          ----------
          symbol: str, or list of str
              the symbol(s) to retrieve
          chunk_range: object
              corresponding range object for the specified chunker (for
              DateChunker it is a DateRange object or a DatetimeIndex,
              as returned by pandas.date_range
          filter_data: boolean
              perform chunk level filtering on the data (see filter in _chunker)
              only applicable when chunk_range is specified
          kwargs: ?
              values passed to the serializer. Varies by serializer
  
          Returns
          -------
          DataFrame or Series, or in the case when multiple symbols are given,
          returns a dict of symbols (symbol -> dataframe/series)
          """
          if not isinstance(symbol, list):
              symbol = [symbol]
  
          sym = self._get_symbol_info(symbol)
          if not sym:
              raise NoDataFoundException('No data found for %s' % (symbol))
  
          spec = {SYMBOL: {'$in': symbol}}
          chunker = CHUNKER_MAP[sym[0][CHUNKER]]
          deser = SER_MAP[sym[0][SERIALIZER]].deserialize
  
          if chunk_range is not None:
              spec.update(chunker.to_mongo(chunk_range))
  
          by_start_segment = [(SYMBOL, pymongo.ASCENDING),
                              (START, pymongo.ASCENDING),
                              (SEGMENT, pymongo.ASCENDING)]
          segment_cursor = self._collection.find(spec, sort=by_start_segment)
  
          chunks = defaultdict(list)
          for _, segments in groupby(segment_cursor, key=lambda x: (x[START], x[SYMBOL])):
              segments = list(segments)
              mdata = self._mdata.find_one({SYMBOL: segments[0][SYMBOL],
                                            START: segments[0][START],
                                            END: segments[0][END]})
  
              # when len(segments) == 1, this is essentially a no-op
              # otherwise, take all segments and reassemble the data to one chunk
              chunk_data = b''.join([doc[DATA] for doc in segments])
              chunks[segments[0][SYMBOL]].append({DATA: chunk_data, METADATA: mdata})
  
          skip_filter = not filter_data or chunk_range is None
  
          if len(symbol) > 1:
              return {sym: deser(chunks[sym], **kwargs) if skip_filter else chunker.filter(deser(chunks[sym], **kwargs), chunk_range) for sym in symbol}
          else:
              return deser(chunks[symbol[0]], **kwargs) if skip_filter else chunker.filter(deser(chunks[symbol[0]], **kwargs), chunk_range)