def merge(
        self,
        right_ds: "DataChain",
        on: Union[MergeColType, Sequence[MergeColType]],
        right_on: Optional[Union[MergeColType, Sequence[MergeColType]]] = None,
        inner=False,
        full=False,
        rname="right_",
    ) -> "Self":
        """Merge two chains based on the specified criteria.

        Parameters:
            right_ds: Chain to join with.
            on: Predicate ("column.name", C("column.name"), or Func) or list of
                Predicates to join on. If both chains have the same predicates then
                this predicate is enough for the join. Otherwise, `right_on` parameter
                has to specify the predicates for the other chain.
            right_on: Optional predicate or list of Predicates for the `right_ds`
                to join.
            inner (bool): Whether to run inner join or outer join.
            full (bool): Whether to run full outer join.
            rname (str): Name prefix for conflicting signal names.

        Examples:
            ```py
            meta = meta_emd.merge(meta_pq, on=(C.name, C.emd__index),
                                  right_on=(C.name, C.pq__index))
            ```

            ```py
            imgs.merge(captions,
                       on=func.path.file_stem(imgs.c("file.path")),
                       right_on=func.path.file_stem(captions.c("file.path"))
            ```
        )
        """
        if on is None:
            raise DatasetMergeError(["None"], None, "'on' must be specified")

        on = _validate_merge_on(on, self)
        if not on:
            raise DatasetMergeError(
                on,
                right_on,
                (
                    "'on' must be 'str', 'Func' or 'Sequence' object "
                    f"but got type '{type(on)}'"
                ),
            )

        if right_on is not None:
            right_on = _validate_merge_on(right_on, right_ds)
            if not right_on:
                raise DatasetMergeError(
                    on,
                    right_on,
                    "'right_on' must be 'str', 'Func' or 'Sequence' object"
                    f" but got type '{type(right_on)}'",
                )

            if len(right_on) != len(on):
                raise DatasetMergeError(
                    on, right_on, "'on' and 'right_on' must have the same length'"
                )

        if self == right_ds:
            right_ds = right_ds.clone()

        errors = []

        def _resolve(
            ds: DataChain,
            col: Union[str, Function, sqlalchemy.ColumnElement],
            side: Union[str, None],
        ):
            try:
                if isinstance(col, Function):
                    return ds.c(col.get_column())
                return ds.c(col) if isinstance(col, (str, C)) else col
            except ValueError:
                if side:
                    errors.append(f"{_get_merge_error_str(col)} in {side}")

        ops = [
            _resolve(self, left, "left")
            == _resolve(right_ds, right, "right" if right_on else None)
            for left, right in zip(on, right_on or on)
        ]

        if errors:
            raise DatasetMergeError(
                on, right_on, f"Could not resolve {', '.join(errors)}"
            )

        query = self._query.join(
            right_ds._query, sqlalchemy.and_(*ops), inner, full, rname + "{name}"
        )
        query.feature_schema = None
        ds = self._evolve(query=query)

        signals_schema = self.signals_schema.clone_without_sys_signals()
        right_signals_schema = right_ds.signals_schema.clone_without_sys_signals()
        ds.signals_schema = SignalSchema({"sys": Sys}) | signals_schema.merge(
            right_signals_schema, rname
        )

        return ds
Merge two chains based on the specified criteria.

        Parameters:
            right_ds: Chain to join with.
            on: Predicate ("column.name", C("column.name"), or Func) or list of
                Predicates to join on. If both chains have the same predicates then
                this predicate is enough for the join. Otherwise, `right_on` parameter
                has to specify the predicates for the other chain.
            right_on: Optional predicate or list of Predicates for the `right_ds`
                to join.
            inner (bool): Whether to run inner join or outer join.
            full (bool): Whether to run full outer join.
            rname (str): Name prefix for conflicting signal names.

        Examples:
            ```py
            meta = meta_emd.merge(meta_pq, on=(C.name, C.emd__index),
                                  right_on=(C.name, C.pq__index))
            ```

            ```py
            imgs.merge(captions,
                       on=func.path.file_stem(imgs.c("file.path")),
                       right_on=func.path.file_stem(captions.c("file.path"))
            ```
        )
        
src/datachain/lib/dc/datachain.py