class IndexQuery implements FlattenQuery<R> {

        // One IdHolder each sub-query
        private final IdHolderList holders;
        // Fetching ids size each time, default 100
        private final long batchSize;

        public IndexQuery(IdHolderList holders, long batchSize) {
            this.holders = holders;
            this.batchSize = batchSize;
        }

        @Override
        public QueryResults<R> iterator() {
            // Iterate all
            if (this.holders.size() == 1) {
                return this.each(this.holders.get(0));
            }
            return QueryResults.flatMap(this.holders.iterator(), this::each);
        }

        private QueryResults<R> each(IdHolder holder) {
            assert !holder.paging();
            Query bindQuery = holder.query();
            this.updateResultsFilter(bindQuery);
            this.updateOffsetIfNeeded(bindQuery);

            // Iterate by all
            if (holder instanceof FixedIdHolder) {
                // The search or joint index query may come here.
                Set<Id> ids = holder.all();
                ids = bindQuery.skipOffsetIfNeeded(ids);
                if (ids.isEmpty()) {
                    return null;
                }

                /*
                 * Sort by input ids because search index results need to keep
                 * in order by ids weight. In addition all the ids (IdQuery)
                 * can be collected by upper layer.
                 */
                return this.queryByIndexIds(ids, holder.keepOrder());
            }

            // Iterate by batch
            assert holder instanceof BatchIdHolder;
            return QueryResults.flatMap((BatchIdHolder) holder, h -> {
                assert ((BatchIdHolder) holder).hasNext();
                long remaining = bindQuery.remaining();
                assert remaining >= 0L || remaining == Query.NO_LIMIT;
                if (remaining > this.batchSize || remaining == Query.NO_LIMIT) {
                    /*
                     * Avoid too many ids in one time query,
                     * Assume it will get one result by each id
                     */
                    remaining = this.batchSize;
                }
                Set<Id> ids = h.fetchNext(null, remaining).ids();
                ids = bindQuery.skipOffsetIfNeeded(ids);
                if (ids.isEmpty()) {
                    return null;
                }

                return this.queryByIndexIds(ids);
            });
        }

        @Override
        public PageResults<R> iterator(int index, String page, long pageSize) {
            // Iterate by paging
            E.checkArgument(0 <= index && index <= this.holders.size(),
                            "Invalid page index %s", index);
            IdHolder holder = this.holders.get(index);
            Query bindQuery = holder.query();
            this.updateResultsFilter(bindQuery);
            PageIds pageIds = holder.fetchNext(page, pageSize);
            if (pageIds.empty()) {
                return PageResults.emptyIterator();
            }

            QueryResults<R> results = this.queryByIndexIds(pageIds.ids());

            return new PageResults<>(results, pageIds.pageState());
        }

        @Override
        public int total() {
            return this.holders.size();
        }

        @Override
        public String toString() {
            return String.format("IndexQuery{%s}", this.holders);
        }

        private void updateOffsetIfNeeded(Query query) {<FILL_FUNCTION_BODY>}

        private void updateResultsFilter(Query query) {
            while (query != null) {
                if (query instanceof ConditionQuery) {
                    ((ConditionQuery) query).updateResultsFilter();
                    return;
                }
                query = query.originQuery();
            }
        }

        private QueryResults<R> queryByIndexIds(Set<Id> ids) {
            return this.queryByIndexIds(ids, false);
        }

        private QueryResults<R> queryByIndexIds(Set<Id> ids, boolean inOrder) {
            IdQuery query = new IdQuery(parent(), ids);
            query.mustSortByInput(inOrder);
            return fetcher().apply(query);
        }
    }

            Query parent = parent();
            assert parent instanceof ConditionQuery;
            OptimizedType optimized = ((ConditionQuery) parent).optimized();
            if (optimized == OptimizedType.INDEX_FILTER) {
                return;
            }
            // Others sub-query may update parent offset, so copy to this query
            query.copyOffset(parent);
        
<no_super_class>