private void migrate(final Connection connection) throws Exception {
        if (DbUtil.doesTableExist(connection, "OLD_IDX_VOL")) {

            // Start by getting all old volumes.
            final Map<Integer, OldVolume> oldVolumes = getOldVolumes(connection);

            // Get a map of index UUIDs to the list of volume ids for each index.
            // All sets in the map will be non empty
            final Map<String, Set<Integer>> indexUuidToVolumeIdListMap = getVolumesToMigrate(connection);

            if (!indexUuidToVolumeIdListMap.isEmpty()) {
                final Map<List<Integer>, VolumeGroup> uniqueIndexVolumesToGroupIdMap = new HashMap<>();
                final Map<String, VolumeGroup> indexUuidToVolumeGroupMap = new HashMap<>();

                // Create sets of unique groups.
                LOGGER.info("Creating index volume groups");
                for (final Entry<String, Set<Integer>> entry : indexUuidToVolumeIdListMap.entrySet()) {
                    final String indexUuid = entry.getKey();
                    final Set<Integer> indexVolIdSet = entry.getValue();

                    // Create a sorted list of the volume ids to ensure comparison as a map key
                    final List<Integer> sortedIndexVolumeIds = indexVolIdSet
                            .stream()
                            .sorted()
                            .collect(Collectors.toUnmodifiableList());

                    if (sortedIndexVolumeIds.isEmpty()) {
                        throw new RuntimeException("sortedIndexVolumeIds is empty for index uuid " + indexUuid);
                    }

                    // Get or create a new volume group for the volumes.
                    final VolumeGroup volumeGroup = uniqueIndexVolumesToGroupIdMap.computeIfAbsent(
                            sortedIndexVolumeIds,
                            sortedIndexVolIds2 ->
                                    createVolumeGroup(connection, sortedIndexVolIds2));

                    Objects.requireNonNull(volumeGroup, "Null volume group");

                    indexUuidToVolumeGroupMap.put(indexUuid, volumeGroup);
                }

                // Create and attach index volumes to groups.
                LOGGER.info("Migrating index volumes");
                createIndexVolumes(connection, uniqueIndexVolumesToGroupIdMap.values(), oldVolumes);

                // Now assign the index volume group to each index.
                LOGGER.info("Migrating index docs");
                migrateIndexDocs(connection, indexUuidToVolumeGroupMap);

                // Migrate shards.
                LOGGER.info("Migrating index shards");
                copyIndexShards(connection, indexUuidToVolumeGroupMap, oldVolumes);

            } else {
                LOGGER.info("OLD_IDX_VOL table is empty so nothing to migrate");
            }
        } else {
            LOGGER.info("OLD_IDX_VOL table doesn't exist so nothing to migrate");
        }
    }
This method migrates index volumes to their new table, and creates index volume groups.
<p>
Index volume groups are new so it will create a new index volume group for each index.
The user can easily sort out how they want to organise their volumes after that.
<p>
We might end up creating several index volumes where a single one existed before. The
relationship required is one-to-many, not many-to-many, so we don't have a choice about this.
stroom-legacy/stroom-legacy-db-migration/src/main/java/stroom/legacy/db/migration/V07_00_00_1502__Index.java