def update(
        self,
        other_data: CoverageData,
        map_path: Callable[[str], str] | None = None,
    ) -> None:
        """Update this data with data from another :class:`CoverageData`.

        If `map_path` is provided, it's a function that re-map paths to match
        the local machine's.  Note: `map_path` is None only when called
        directly from the test suite.

        """
        if self._debug.should("dataop"):
            self._debug.write("Updating with data from {!r}".format(
                getattr(other_data, "_filename", "???"),
            ))
        if self._has_lines and other_data._has_arcs:
            raise DataError("Can't combine branch coverage data with statement data")
        if self._has_arcs and other_data._has_lines:
            raise DataError("Can't combine statement coverage data with branch data")

        map_path = map_path or (lambda p: p)

        # Force the database we're writing to to exist before we start nesting contexts.
        self._start_using()

        # Collector for all arcs, lines and tracers
        other_data.read()
        with other_data._connect() as con:
            # Get files data.
            with con.execute("select path from file") as cur:
                files = {path: map_path(path) for (path,) in cur}

            # Get contexts data.
            with con.execute("select context from context") as cur:
                contexts = [context for (context,) in cur]

            # Get arc data.
            with con.execute(
                "select file.path, context.context, arc.fromno, arc.tono " +
                "from arc " +
                "inner join file on file.id = arc.file_id " +
                "inner join context on context.id = arc.context_id",
            ) as cur:
                arcs = [
                    (files[path], context, fromno, tono)
                    for (path, context, fromno, tono) in cur
                ]

            # Get line data.
            with con.execute(
                "select file.path, context.context, line_bits.numbits " +
                "from line_bits " +
                "inner join file on file.id = line_bits.file_id " +
                "inner join context on context.id = line_bits.context_id",
            ) as cur:
                lines: dict[tuple[str, str], bytes] = {}
                for path, context, numbits in cur:
                    key = (files[path], context)
                    if key in lines:
                        numbits = numbits_union(lines[key], numbits)
                    lines[key] = numbits

            # Get tracer data.
            with con.execute(
                "select file.path, tracer " +
                "from tracer " +
                "inner join file on file.id = tracer.file_id",
            ) as cur:
                tracers = {files[path]: tracer for (path, tracer) in cur}

        with self._connect() as con:
            assert con.con is not None
            con.con.isolation_level = "IMMEDIATE"

            # Get all tracers in the DB. Files not in the tracers are assumed
            # to have an empty string tracer. Since Sqlite does not support
            # full outer joins, we have to make two queries to fill the
            # dictionary.
            with con.execute("select path from file") as cur:
                this_tracers = {path: "" for path, in cur}
            with con.execute(
                "select file.path, tracer from tracer " +
                "inner join file on file.id = tracer.file_id",
            ) as cur:
                this_tracers.update({
                    map_path(path): tracer
                    for path, tracer in cur
                })

            # Create all file and context rows in the DB.
            con.executemany_void(
                "insert or ignore into file (path) values (?)",
                ((file,) for file in files.values()),
            )
            with con.execute("select id, path from file") as cur:
                file_ids = {path: id for id, path in cur}
            self._file_map.update(file_ids)
            con.executemany_void(
                "insert or ignore into context (context) values (?)",
                ((context,) for context in contexts),
            )
            with con.execute("select id, context from context") as cur:
                context_ids = {context: id for id, context in cur}

            # Prepare tracers and fail, if a conflict is found.
            # tracer_paths is used to ensure consistency over the tracer data
            # and tracer_map tracks the tracers to be inserted.
            tracer_map = {}
            for path in files.values():
                this_tracer = this_tracers.get(path)
                other_tracer = tracers.get(path, "")
                # If there is no tracer, there is always the None tracer.
                if this_tracer is not None and this_tracer != other_tracer:
                    raise DataError(
                        "Conflicting file tracer name for '{}': {!r} vs {!r}".format(
                            path, this_tracer, other_tracer,
                        ),
                    )
                tracer_map[path] = other_tracer

            # Prepare arc and line rows to be inserted by converting the file
            # and context strings with integer ids. Then use the efficient
            # `executemany()` to insert all rows at once.

            if arcs:
                self._choose_lines_or_arcs(arcs=True)

                arc_rows = (
                    (file_ids[file], context_ids[context], fromno, tono)
                    for file, context, fromno, tono in arcs
                )

                # Write the combined data.
                con.executemany_void(
                    "insert or ignore into arc " +
                    "(file_id, context_id, fromno, tono) values (?, ?, ?, ?)",
                    arc_rows,
                )

            if lines:
                self._choose_lines_or_arcs(lines=True)

                for (file, context), numbits in lines.items():
                    with con.execute(
                        "select numbits from line_bits where file_id = ? and context_id = ?",
                        (file_ids[file], context_ids[context]),
                    ) as cur:
                        existing = list(cur)
                    if existing:
                        lines[(file, context)] = numbits_union(numbits, existing[0][0])

                con.executemany_void(
                    "insert or replace into line_bits " +
                    "(file_id, context_id, numbits) values (?, ?, ?)",
                    [
                        (file_ids[file], context_ids[context], numbits)
                        for (file, context), numbits in lines.items()
                    ],
                )

            con.executemany_void(
                "insert or ignore into tracer (file_id, tracer) values (?, ?)",
                ((file_ids[filename], tracer) for filename, tracer in tracer_map.items()),
            )

        if not self._no_disk:
            # Update all internal cache data.
            self._reset()
            self.read()
Update this data with data from another :class:`CoverageData`.

        If `map_path` is provided, it's a function that re-map paths to match
        the local machine's.  Note: `map_path` is None only when called
        directly from the test suite.

        
coverage/sqldata.py