001/*-
002 * #%L
003 * HAPI FHIR Server - SQL Migration
004 * %%
005 * Copyright (C) 2014 - 2025 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.jpa.migrate.taskdef;
021
022import org.apache.commons.lang3.builder.EqualsBuilder;
023import org.apache.commons.lang3.builder.HashCodeBuilder;
024import org.apache.commons.lang3.tuple.Pair;
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027import org.springframework.jdbc.core.JdbcTemplate;
028
029import java.sql.SQLException;
030
031public class ForceIdMigrationCopyTask extends BaseTask {
032        private static final Logger ourLog = LoggerFactory.getLogger(ForceIdMigrationCopyTask.class);
033
034        public ForceIdMigrationCopyTask(String theProductVersion, String theSchemaVersion) {
035                super(theProductVersion, theSchemaVersion);
036        }
037
038        @Override
039        public void validate() {
040                // no-op
041        }
042
043        @Override
044        protected void doExecute() throws SQLException {
045                logInfo(ourLog, "Starting: migrate fhir_id from hfj_forced_id to hfj_resource.fhir_id");
046
047                JdbcTemplate jdbcTemplate = newJdbcTemplate();
048
049                Pair<Long, Long> range = jdbcTemplate.queryForObject(
050                                "select min(RES_ID), max(RES_ID) from HFJ_RESOURCE",
051                                (rs, rowNum) -> Pair.of(rs.getLong(1), rs.getLong(2)));
052
053                if (range == null || range.getLeft() == null) {
054                        logInfo(ourLog, "HFJ_RESOURCE is empty.  No work to do.");
055                        return;
056                }
057
058                // run update in batches.
059                int rowsPerBlock = 50; // hfj_resource has roughly 50 rows per 8k block.
060                int batchSize = rowsPerBlock * 2000; // a few thousand IOPS gives a batch size around a second.
061                for (long batchStart = range.getLeft(); batchStart <= range.getRight(); batchStart = batchStart + batchSize) {
062                        long batchEnd = batchStart + batchSize;
063                        ourLog.info("Migrating client-assigned ids for pids: {}-{}", batchStart, batchEnd);
064
065                        // This should be fast-ish since fhir_id isn't indexed yet,
066                        // and we're walking both hfj_resource and hfj_forced_id in insertion order.
067                        executeSql(
068                                        "hfj_resource",
069                                        "update hfj_resource " + "set fhir_id = coalesce( "
070                                                        + // use first non-null value: forced_id if present, otherwise res_id
071                                                        "   (select f.forced_id from hfj_forced_id f where f.resource_pid = res_id), "
072                                                        + "   cast(res_id as varchar(64)) "
073                                                        + "  ) "
074                                                        + "where fhir_id is null "
075                                                        + "and res_id >= ? and res_id < ?",
076                                        batchStart,
077                                        batchEnd);
078                }
079        }
080
081        @Override
082        protected void generateHashCode(HashCodeBuilder theBuilder) {
083                // no-op - this is a singleton.
084        }
085
086        @Override
087        protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) {
088                // no-op - this is a singleton.
089        }
090}