public FeatureCollection<SimpleFeatureType, SimpleFeature> getLog(
      String fromVersion, String toVersion, Filter filter, String[] userIds, int maxRows)
      throws IOException {
    if (filter == null) filter = Filter.INCLUDE;
    RevisionInfo r1 = new RevisionInfo(fromVersion);
    RevisionInfo r2 = new RevisionInfo(toVersion);

    boolean swapped = false;
    if (r1.revision > r2.revision) {
      // swap them
      RevisionInfo tmpr = r1;
      r1 = r2;
      r2 = tmpr;
      String tmps = toVersion;
      toVersion = fromVersion;
      fromVersion = tmps;
      swapped = true;
    }

    // We implement this exactly as described. Happily, it seems Postgis does not have
    // sql lentgh limitations. Yet, if would be a lot better if we could encode this
    // as a single sql query with subqueries... (but not all filters are encodable...)
    ModifiedFeatureIds mfids =
        store.getModifiedFeatureFIDs(
            schema.getTypeName(), fromVersion, toVersion, filter, userIds, getTransaction());
    Set ids = new HashSet(mfids.getCreated());
    ids.addAll(mfids.getDeleted());
    ids.addAll(mfids.getModified());

    // grab the eventually modified revisions from mfids
    r1 = mfids.fromRevision;
    r2 = mfids.toRevision;

    // no changes?
    if (ids.isEmpty()) return new EmptyFeatureCollection(schema);

    // Create a filter that sounds like:
    // (revision > r1 and revision <= r2) or (expired > r1 and expired <= r2) and fid in
    // (fidlist)
    FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
    Filter fidFilter = store.buildFidFilter(ids);
    Filter transformedFidFilter = store.transformFidFilter(schema.getTypeName(), fidFilter);
    Filter revGrR1 = ff.greater(ff.property("revision"), ff.literal(r1.revision));
    Filter revLeR2 = ff.lessOrEqual(ff.property("revision"), ff.literal(r2.revision));
    Filter expGrR1 = ff.greater(ff.property("expired"), ff.literal(r1.revision));
    Filter expLeR2 = ff.lessOrEqual(ff.property("expired"), ff.literal(r2.revision));
    Filter versionFilter =
        ff.and(transformedFidFilter, ff.or(ff.and(revGrR1, revLeR2), ff.and(expGrR1, expLeR2)));

    // We just want the revision and expired, build a query against the real feature type
    DefaultQuery q =
        new DefaultQuery(schema.getTypeName(), versionFilter, new String[] {"revision", "expired"});
    FeatureReader<SimpleFeatureType, SimpleFeature> fr = null;
    SortedSet revisions = new TreeSet();
    try {
      fr = store.wrapped.getFeatureReader(q, getTransaction());
      while (fr.hasNext()) {
        SimpleFeature f = fr.next();
        Long revision = (Long) f.getAttribute(0);
        if (revision.longValue() > r1.revision) revisions.add(revision);
        Long expired = (Long) f.getAttribute(1);
        if (expired.longValue() != Long.MAX_VALUE && expired.longValue() > r1.revision)
          revisions.add(expired);
      }
    } catch (Exception e) {
      throw new DataSourceException("Error reading modified revisions from datastore", e);
    } finally {
      if (fr != null) fr.close();
    }

    // now, we have a list of revisions between a min and a max
    // let's try to build a fid filter with revisions from the biggest to the smallest
    Set revisionIdSet = new HashSet();
    for (Iterator it = revisions.iterator(); it.hasNext(); ) {
      Long rev = (Long) it.next();
      revisionIdSet.add(
          ff.featureId(VersionedPostgisDataStore.TBL_CHANGESETS + "." + rev.toString()));
    }
    if (revisionIdSet.isEmpty()) return new EmptyFeatureCollection(schema);
    Filter revisionFilter = ff.id(revisionIdSet);

    // return the changelog
    // TODO: sort on revision descending. Unfortunately, to do so we have to fix fid mappers,
    // so that auto-increment can return revision among the attributes, and at the same
    // time simply allow not include fid attributes in the insert queries (or provide a
    // "default"
    // value for them).
    FeatureSource<SimpleFeatureType, SimpleFeature> changesets =
        (FeatureSource<SimpleFeatureType, SimpleFeature>)
            store.getFeatureSource(VersionedPostgisDataStore.TBL_CHANGESETS);
    DefaultQuery sq = new DefaultQuery();
    sq.setFilter(revisionFilter);
    final SortOrder order = swapped ? SortOrder.ASCENDING : SortOrder.DESCENDING;
    sq.setSortBy(new SortBy[] {ff.sort("revision", order)});
    if (maxRows > 0) sq.setMaxFeatures(maxRows);
    return changesets.getFeatures(sq);
  }