/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::borrow::Cow;
use std::collections::BTreeMap;
use std::collections::HashMap;

use anyhow::Context;
use anyhow::Error;
use anyhow::Result;
use blobstore::Blobstore;
use blobstore::StoreLoadable;
use borrowed::borrowed;
use bounded_traversal::bounded_traversal;
use context::CoreContext;
use fbthrift::compact_protocol;
use futures::future::try_join;
use futures::future::try_join_all;
use futures::future::FutureExt;
use futures::stream;
use futures::stream::StreamExt;
use futures::stream::TryStreamExt;
use sorted_vector_map::SortedVectorMap;

use crate::blob::Blob;
use crate::blob::BlobstoreValue;
use crate::blob::SkeletonManifestBlob;
use crate::errors::ErrorKind;
use crate::path::MPath;
use crate::path::MPathElement;
use crate::thrift;
use crate::typed_hash::SkeletonManifestId;
use crate::typed_hash::SkeletonManifestIdContext;

/// A skeleton manifest is a manifest node containing summary information about the
/// the structure of files (their names, but not their contents) that is useful
/// for traversing manifests and for determining case conflicts and path name
/// compatibility.
///
/// Skeleton manifests only exist for trees, and each skeleton manifest is a structure that contains:
/// * A list of its children, containing for each child:
///   - Name
///   - Whether is is a directory or not
///   - The skeleton manifest id, summary flags counts for directories.
/// * The summary flags and counts for the directory itself.
///
/// The summary flags stored for each directory are:
/// * whether the directory's immediate children contain a case conflict.
/// * whether any descendant directories contain a case conflict.
/// * whether the directory's immediate children include a filename that is
///   invalid on Windows.
/// * whether the descendant directories contain a filename that is invalid on
///   Windows.
///
/// The summary counts stored for each directory are:
/// * recursive count of descendant sub-directories
/// * maximum path length in the directory
/// * maximum path element length for any child directory
/// * maximum path element length for the contents of descendant directories
///
/// Path element and path lengths are measured in bytes.
///
/// Unlike unodes, skeleton manifests are not repository-wide unique. Unlike
/// fsnodes, they are not content-addressed.  If the same set of file names and
/// directory names appear at different places in the commit graph, they will
/// share skeleton manifests.

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct SkeletonManifest {
    subentries: SortedVectorMap<MPathElement, SkeletonManifestEntry>,
    summary: SkeletonManifestSummary,
}

impl SkeletonManifest {
    pub fn new(
        subentries: SortedVectorMap<MPathElement, SkeletonManifestEntry>,
        summary: SkeletonManifestSummary,
    ) -> Self {
        Self {
            subentries,
            summary,
        }
    }

    pub fn lookup(&self, basename: &MPathElement) -> Option<&SkeletonManifestEntry> {
        self.subentries.get(basename)
    }

    pub fn list(&self) -> impl Iterator<Item = (&MPathElement, &SkeletonManifestEntry)> {
        self.subentries.iter()
    }

    pub fn into_subentries(self) -> SortedVectorMap<MPathElement, SkeletonManifestEntry> {
        self.subentries
    }

    pub fn summary(&self) -> &SkeletonManifestSummary {
        &self.summary
    }

    pub fn has_case_conflicts(&self) -> bool {
        self.summary.child_case_conflicts || self.summary.descendant_case_conflicts
    }

    pub async fn first_case_conflict<'a>(
        &'a self,
        ctx: &'a CoreContext,
        blobstore: &'a impl Blobstore,
    ) -> Result<Option<(MPath, MPath)>> {
        let mut sk_mf = Cow::Borrowed(self);
        let mut path: Option<MPath> = None;
        'outer: loop {
            if sk_mf.summary.child_case_conflicts {
                let mut lower_map = HashMap::new();
                for name in sk_mf.subentries.keys() {
                    if let Some(lower_name) = name.to_lowercase_utf8() {
                        if let Some(other_name) = lower_map.insert(lower_name, name.clone()) {
                            return Ok(Some((
                                MPath::join_opt_element(path.as_ref(), &other_name),
                                MPath::join_opt_element(path.as_ref(), name),
                            )));
                        }
                    }
                }
            }
            if sk_mf.summary.descendant_case_conflicts {
                for (name, entry) in sk_mf.subentries.iter() {
                    if let SkeletonManifestEntry::Directory(subdir) = entry {
                        if subdir.summary.child_case_conflicts
                            || subdir.summary.descendant_case_conflicts
                        {
                            path = Some(MPath::join_opt_element(path.as_ref(), name));
                            sk_mf = Cow::Owned(subdir.id.load(ctx, blobstore).await?);
                            continue 'outer;
                        }
                    }
                }
            }
            return Ok(None);
        }
    }

    /// Returns the first case conflict that wasn't present in any of the
    /// parents.
    pub async fn first_new_case_conflict<'a>(
        self,
        ctx: &'a CoreContext,
        blobstore: &'a impl Blobstore,
        parents: Vec<SkeletonManifest>,
    ) -> Result<Option<(MPath, MPath)>> {
        bounded_traversal(
            256,
            (None, self, parents),
            |(path, sk_mf, parents)| {
                async move {
                    if sk_mf.summary.child_case_conflicts {
                        if let Some((name1, name2)) = sk_mf.first_new_child_case_conflict(&parents)
                        {
                            let path1 = MPath::join_opt_element(path.as_ref(), name1);
                            let path2 = MPath::join_opt_element(path.as_ref(), name2);
                            // Since we only want the first conflict, don't
                            // recurse to child directories.
                            return Ok((Some((path1, path2)), Vec::new()));
                        }
                    }

                    if !sk_mf.summary.descendant_case_conflicts {
                        return Ok((None, Vec::new()));
                    }

                    borrowed!(path);
                    let recurse_ids = sk_mf
                        .recurse_new_descendant_case_conflicts(&parents)
                        .map(|(name, recurse_id, recurse_parent_ids)| async move {
                            let recurse_path = MPath::join_opt_element(path.as_ref(), name);
                            let (recurse_sk_mf, recurse_parents) = try_join(
                                recurse_id.load(ctx, blobstore),
                                try_join_all(
                                    recurse_parent_ids
                                        .into_iter()
                                        .map(|id| async move { id.load(ctx, blobstore).await }),
                                ),
                            )
                            .await?;
                            Ok::<_, Error>((Some(recurse_path), recurse_sk_mf, recurse_parents))
                        })
                        .collect::<Vec<_>>();

                    let recurse = stream::iter(recurse_ids)
                        .buffered(100)
                        .try_collect::<Vec<_>>()
                        .await?;

                    Ok((None, recurse))
                }
                .boxed()
            },
            |maybe_conflict, child_conflicts| {
                async move {
                    Ok(maybe_conflict.or_else(move || {
                        for conflict in child_conflicts {
                            if let Some(conflict) = conflict {
                                return Some(conflict);
                            }
                        }
                        None
                    }))
                }
                .boxed()
            },
        )
        .await
    }

    /// Returns the first case conflict that is an immediate child of this
    /// skeleton manifest that is not present in any of the parents.
    fn first_new_child_case_conflict(
        &self,
        parents: &[SkeletonManifest],
    ) -> Option<(&MPathElement, &MPathElement)> {
        let mut lower_map: BTreeMap<String, Vec<_>> = BTreeMap::new();
        for name in self.subentries.keys() {
            if let Some(lower_name) = name.to_lowercase_utf8() {
                lower_map.entry(lower_name).or_default().push(name)
            }
        }

        for (_lower_name, names) in lower_map.iter() {
            match names.as_slice() {
                [name1, name2, ..] => {
                    // These names form a case conflict.  All names
                    // must exist in at least one parent,
                    // otherwise this commit introduced a case
                    // conflict.
                    let conflict_exists_in_parent = parents.iter().any(|parent| {
                        names
                            .iter()
                            .all(|name| parent.subentries.contains_key(name))
                    });
                    if !conflict_exists_in_parent {
                        return Some((name1, name2));
                    }
                }
                _ => {}
            }
        }

        None
    }

    /// Returns the skeleton manifests to recurse into to check for case
    /// conflicts in descendant directories that are not in any of the
    /// parents.
    ///
    /// Returns an iterator of (name, subdir_id, subdir_parent_ids) for each
    /// subentry that might have descendant case conflicts, where the parent
    /// ids are the ids of the corresponding parent subdirectories that might
    /// also have case conflicts.
    fn recurse_new_descendant_case_conflicts<'s>(
        &'s self,
        parents: &'s [SkeletonManifest],
    ) -> impl Iterator<
        Item = (
            &'s MPathElement,
            SkeletonManifestId,
            Vec<SkeletonManifestId>,
        ),
    > {
        self.subentries.iter().filter_map(move |(name, entry)| {
            if let SkeletonManifestEntry::Directory(subdir) = entry {
                if !subdir.has_case_conflicts() {
                    return None;
                }

                // Recurse into this subdirectory to check its case
                // conflicts.  Include only the parent subentries that
                // contain case conflicts, as conflict-free parents can be
                // ignored.
                let recurse_id = subdir.id;
                let recurse_parent_ids: Vec<_> = parents
                    .iter()
                    .filter_map(|parent| {
                        if let Some(SkeletonManifestEntry::Directory(subdir)) =
                            parent.subentries.get(name)
                        {
                            if subdir.has_case_conflicts() {
                                return Some(subdir.id);
                            }
                        }
                        None
                    })
                    .collect();

                return Some((name, recurse_id, recurse_parent_ids));
            }
            None
        })
    }

    pub(crate) fn from_thrift(t: thrift::SkeletonManifest) -> Result<SkeletonManifest> {
        let subentries = t
            .subentries
            .into_iter()
            .map(|(basename, skeleton_entry)| {
                let basename = MPathElement::from_thrift(basename)?;
                let skeleton_entry = SkeletonManifestEntry::from_thrift(skeleton_entry)?;
                Ok((basename, skeleton_entry))
            })
            .collect::<Result<_>>()?;
        let summary = SkeletonManifestSummary::from_thrift(t.summary)?;
        Ok(SkeletonManifest {
            subentries,
            summary,
        })
    }

    pub(crate) fn into_thrift(self) -> thrift::SkeletonManifest {
        let subentries: SortedVectorMap<_, _> = self
            .subentries
            .into_iter()
            .map(|(basename, fsnode_entry)| (basename.into_thrift(), fsnode_entry.into_thrift()))
            .collect();
        let summary = self.summary.into_thrift();
        thrift::SkeletonManifest {
            subentries,
            summary,
        }
    }

    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
        let thrift_tc = compact_protocol::deserialize(bytes)
            .with_context(|| ErrorKind::BlobDeserializeError("SkeletonManifest".into()))?;
        Self::from_thrift(thrift_tc)
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum SkeletonManifestEntry {
    File,
    Directory(SkeletonManifestDirectory),
}

impl SkeletonManifestEntry {
    pub(crate) fn from_thrift(t: thrift::SkeletonManifestEntry) -> Result<SkeletonManifestEntry> {
        match t.directory {
            None => Ok(SkeletonManifestEntry::File),
            Some(skeleton_directory) => {
                let skeleton_directory =
                    SkeletonManifestDirectory::from_thrift(skeleton_directory)?;
                Ok(SkeletonManifestEntry::Directory(skeleton_directory))
            }
        }
    }

    pub(crate) fn into_thrift(self) -> thrift::SkeletonManifestEntry {
        let directory = match self {
            SkeletonManifestEntry::File => None,
            SkeletonManifestEntry::Directory(skeleton_directory) => {
                Some(skeleton_directory.into_thrift())
            }
        };
        thrift::SkeletonManifestEntry { directory }
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct SkeletonManifestDirectory {
    id: SkeletonManifestId,
    summary: SkeletonManifestSummary,
}

impl SkeletonManifestDirectory {
    pub fn new(id: SkeletonManifestId, summary: SkeletonManifestSummary) -> Self {
        Self { id, summary }
    }

    pub fn id(&self) -> &SkeletonManifestId {
        &self.id
    }

    pub fn summary(&self) -> &SkeletonManifestSummary {
        &self.summary
    }

    pub fn has_case_conflicts(&self) -> bool {
        self.summary.child_case_conflicts || self.summary.descendant_case_conflicts
    }

    pub(crate) fn from_thrift(
        t: thrift::SkeletonManifestDirectory,
    ) -> Result<SkeletonManifestDirectory> {
        let id = SkeletonManifestId::from_thrift(t.id)?;
        let summary = SkeletonManifestSummary::from_thrift(t.summary)?;
        Ok(SkeletonManifestDirectory { id, summary })
    }

    pub(crate) fn into_thrift(self) -> thrift::SkeletonManifestDirectory {
        thrift::SkeletonManifestDirectory {
            id: self.id.into_thrift(),
            summary: self.summary.into_thrift(),
        }
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
pub struct SkeletonManifestSummary {
    pub child_files_count: u64,
    pub descendant_files_count: u64,
    pub child_dirs_count: u64,
    pub descendant_dirs_count: u64,
    pub max_path_len: u32,
    pub max_path_wchar_len: u32,
    pub child_case_conflicts: bool,
    pub descendant_case_conflicts: bool,
    pub child_non_utf8_filenames: bool,
    pub descendant_non_utf8_filenames: bool,
    pub child_invalid_windows_filenames: bool,
    pub descendant_invalid_windows_filenames: bool,
}

impl SkeletonManifestSummary {
    pub(crate) fn from_thrift(
        t: thrift::SkeletonManifestSummary,
    ) -> Result<SkeletonManifestSummary> {
        Ok(SkeletonManifestSummary {
            child_files_count: t.child_files_count as u64,
            descendant_files_count: t.descendant_files_count as u64,
            child_dirs_count: t.child_dirs_count as u64,
            descendant_dirs_count: t.descendant_dirs_count as u64,
            max_path_len: t.max_path_len as u32,
            max_path_wchar_len: t.max_path_wchar_len as u32,
            child_case_conflicts: t.child_case_conflicts,
            descendant_case_conflicts: t.descendant_case_conflicts,
            child_non_utf8_filenames: t.child_non_utf8_filenames,
            descendant_non_utf8_filenames: t.descendant_non_utf8_filenames,
            child_invalid_windows_filenames: t.child_invalid_windows_filenames,
            descendant_invalid_windows_filenames: t.descendant_invalid_windows_filenames,
        })
    }

    pub(crate) fn into_thrift(self) -> thrift::SkeletonManifestSummary {
        thrift::SkeletonManifestSummary {
            child_files_count: self.child_files_count as i64,
            descendant_files_count: self.descendant_files_count as i64,
            child_dirs_count: self.child_dirs_count as i64,
            descendant_dirs_count: self.descendant_dirs_count as i64,
            max_path_len: self.max_path_len as i32,
            max_path_wchar_len: self.max_path_wchar_len as i32,
            child_case_conflicts: self.child_case_conflicts,
            descendant_case_conflicts: self.descendant_case_conflicts,
            child_non_utf8_filenames: self.child_non_utf8_filenames,
            descendant_non_utf8_filenames: self.descendant_non_utf8_filenames,
            child_invalid_windows_filenames: self.child_invalid_windows_filenames,
            descendant_invalid_windows_filenames: self.descendant_invalid_windows_filenames,
        }
    }
}

impl BlobstoreValue for SkeletonManifest {
    type Key = SkeletonManifestId;

    fn into_blob(self) -> SkeletonManifestBlob {
        let thrift = self.into_thrift();
        let data = compact_protocol::serialize(&thrift);
        let mut context = SkeletonManifestIdContext::new();
        context.update(&data);
        let id = context.finish();
        Blob::new(id, data)
    }

    fn from_blob(blob: Blob<Self::Key>) -> Result<Self> {
        Self::from_bytes(blob.data().as_ref())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use sql::mysql_async::from_value_opt;
use sql::mysql_async::prelude::ConvIr;
use sql::mysql_async::prelude::FromValue;
use sql::mysql_async::FromValueError;
use sql::mysql_async::Value;
use sql::sql_common::mysql::opt_try_from_rowfield;
use sql::sql_common::mysql::OptionalTryFromRowField;
use sql::sql_common::mysql::RowField;
use sql::sql_common::mysql::ValueError;

use crate::datetime::Timestamp;
use crate::globalrev::Globalrev;
use crate::hash::Blake2;
use crate::hash::GitSha1;
use crate::repo::RepositoryId;
use crate::svnrev::Svnrev;
use crate::typed_hash::ChangesetId;

type FromValueResult<T> = Result<T, FromValueError>;

impl From<ChangesetId> for Value {
    fn from(id: ChangesetId) -> Self {
        Value::Bytes(id.as_ref().into())
    }
}

impl ConvIr<ChangesetId> for Blake2 {
    fn new(v: Value) -> FromValueResult<Self> {
        match v {
            Value::Bytes(bytes) => {
                Blake2::from_bytes(&bytes).map_err(move |_| FromValueError(Value::Bytes(bytes)))
            }
            v => Err(FromValueError(v)),
        }
    }

    fn commit(self) -> ChangesetId {
        ChangesetId::new(self)
    }

    fn rollback(self) -> Value {
        Value::Bytes(self.as_ref().into())
    }
}

impl FromValue for ChangesetId {
    type Intermediate = Blake2;
}

impl From<Timestamp> for Value {
    fn from(ts: Timestamp) -> Self {
        Value::Int(ts.timestamp_nanos())
    }
}

impl ConvIr<Timestamp> for Timestamp {
    fn new(v: Value) -> FromValueResult<Self> {
        Ok(Timestamp::from_timestamp_nanos(from_value_opt(v)?))
    }

    fn commit(self) -> Self {
        self
    }

    fn rollback(self) -> Value {
        self.into()
    }
}

impl FromValue for Timestamp {
    type Intermediate = Timestamp;
}

impl From<RepositoryId> for Value {
    fn from(repo_id: RepositoryId) -> Self {
        Value::UInt(repo_id.id() as u64)
    }
}

impl ConvIr<RepositoryId> for RepositoryId {
    fn new(v: Value) -> FromValueResult<Self> {
        Ok(RepositoryId::new(from_value_opt(v)?))
    }

    fn commit(self) -> Self {
        self
    }

    fn rollback(self) -> Value {
        self.into()
    }
}

impl FromValue for RepositoryId {
    type Intermediate = RepositoryId;
}

impl From<GitSha1> for Value {
    fn from(sha1: GitSha1) -> Self {
        Value::Bytes(sha1.as_ref().into())
    }
}

impl ConvIr<GitSha1> for GitSha1 {
    fn new(v: Value) -> FromValueResult<Self> {
        match v {
            Value::Bytes(bytes) => {
                GitSha1::from_bytes(&bytes).map_err(move |_| FromValueError(Value::Bytes(bytes)))
            }
            v => Err(FromValueError(v)),
        }
    }

    fn commit(self) -> Self {
        self
    }

    fn rollback(self) -> Value {
        self.into()
    }
}

impl FromValue for GitSha1 {
    type Intermediate = GitSha1;
}

impl OptionalTryFromRowField for GitSha1 {
    fn try_from_opt(field: RowField) -> Result<Option<Self>, ValueError> {
        opt_try_from_rowfield(field)
    }
}

impl From<Globalrev> for Value {
    fn from(globalrev: Globalrev) -> Self {
        Value::UInt(globalrev.id())
    }
}

impl ConvIr<Globalrev> for Globalrev {
    fn new(v: Value) -> FromValueResult<Self> {
        Ok(Globalrev::new(from_value_opt(v)?))
    }

    fn commit(self) -> Self {
        self
    }

    fn rollback(self) -> Value {
        self.into()
    }
}

impl FromValue for Globalrev {
    type Intermediate = Globalrev;
}

impl From<Svnrev> for Value {
    fn from(svnrev: Svnrev) -> Self {
        Value::UInt(svnrev.id())
    }
}

impl ConvIr<Svnrev> for Svnrev {
    fn new(v: Value) -> FromValueResult<Self> {
        Ok(Svnrev::new(from_value_opt(v)?))
    }

    fn commit(self) -> Self {
        self
    }

    fn rollback(self) -> Value {
        self.into()
    }
}

impl FromValue for Svnrev {
    type Intermediate = Svnrev;
}

impl From<Blake2> for Value {
    fn from(id: Blake2) -> Self {
        Value::Bytes(id.as_ref().into())
    }
}

impl ConvIr<Blake2> for Blake2 {
    fn new(v: Value) -> FromValueResult<Self> {
        match v {
            Value::Bytes(bytes) => {
                Blake2::from_bytes(&bytes).map_err(move |_| FromValueError(Value::Bytes(bytes)))
            }
            v => Err(FromValueError(v)),
        }
    }

    fn commit(self) -> Self {
        self
    }

    fn rollback(self) -> Value {
        self.into()
    }
}

impl FromValue for Blake2 {
    type Intermediate = Blake2;
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::fmt;
use std::fmt::Display;
use std::str;
use std::str::FromStr;

use abomonation_derive::Abomonation;
use anyhow::bail;
use anyhow::Error;
use anyhow::Result;
use sql::mysql;

use crate::BonsaiChangeset;

// Changeset svnrev. Present only in some repos which were imported from SVN.
#[derive(Abomonation, Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[derive(mysql::OptTryFromRowField)]
pub struct Svnrev(u64);

impl Svnrev {
    #[inline]
    pub const fn new(rev: u64) -> Self {
        Self(rev)
    }

    #[inline]
    pub fn id(&self) -> u64 {
        self.0
    }

    // ex. svn:uuid/path@1234
    pub fn parse_svnrev(svnrev: &str) -> Result<u64> {
        let at_pos = svnrev
            .rfind('@')
            .ok_or_else(|| Error::msg("Wrong convert_revision value"))?;
        let result = svnrev[1 + at_pos..].parse::<u64>()?;
        Ok(result)
    }

    pub fn from_bcs(bcs: &BonsaiChangeset) -> Result<Self> {
        match bcs.hg_extra().find(|(key, _)| key == &"convert_revision") {
            Some((_, svnrev)) => {
                let svnrev = Svnrev::parse_svnrev(str::from_utf8(svnrev)?)?;
                Ok(Self::new(svnrev))
            }
            None => bail!("Bonsai cs {:?} without svnrev", bcs),
        }
    }
}

impl Display for Svnrev {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        Display::fmt(&self.0, fmt)
    }
}

impl FromStr for Svnrev {
    type Err = <u64 as FromStr>::Err;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        u64::from_str(s).map(Svnrev::new)
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use anyhow::Context;
use anyhow::Result;
use bufsize::SizeCounter;
use bytes::Bytes;
use bytes::BytesMut;
use fbthrift::compact_protocol;
use fbthrift::compact_protocol::CompactProtocolDeserializer;
use fbthrift::compact_protocol::CompactProtocolSerializer;
use fbthrift::Deserialize;
use fbthrift::Serialize;

use crate::errors::ErrorKind;

pub trait ThriftConvert: Sized {
    const NAME: &'static str;
    type Thrift: Serialize<CompactProtocolSerializer<SizeCounter>>
        + Serialize<CompactProtocolSerializer<BytesMut>>
        + Deserialize<CompactProtocolDeserializer<std::io::Cursor<Bytes>>>;
    fn from_thrift(t: Self::Thrift) -> Result<Self>;
    fn into_thrift(self) -> Self::Thrift;
    fn from_bytes(bytes: &Bytes) -> Result<Self> {
        let thrift = compact_protocol::deserialize(bytes)
            .with_context(|| ErrorKind::BlobDeserializeError(Self::NAME.to_string()))?;
        Self::from_thrift(thrift)
    }
    fn into_bytes(self) -> Bytes {
        compact_protocol::serialize(&self.into_thrift())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::hash::Hash;
use std::result;
use std::str::FromStr;

use abomonation_derive::Abomonation;
use anyhow::Result;
use async_trait::async_trait;
use blobstore::Blobstore;
use blobstore::Loadable;
use blobstore::LoadableError;
use context::CoreContext;
use edenapi_types::BonsaiChangesetId as EdenapiBonsaiChangesetId;
use edenapi_types::CommitId as EdenapiCommitId;
use edenapi_types::ContentId as EdenapiContentId;
use edenapi_types::FsnodeId as EdenapiFsnodeId;
use sql::mysql;

use crate::basename_suffix_skeleton_manifest::BasenameSuffixSkeletonManifest;
use crate::basename_suffix_skeleton_manifest::BssmEntry;
use crate::blob::Blob;
use crate::blob::BlobstoreValue;
use crate::bonsai_changeset::BonsaiChangeset;
use crate::content_chunk::ContentChunk;
use crate::content_metadata_v2::ContentMetadataV2;
use crate::deleted_manifest_v2::DeletedManifestV2;
use crate::fastlog_batch::FastlogBatch;
use crate::file_contents::FileContents;
use crate::fsnode::Fsnode;
use crate::hash::Blake2;
use crate::hash::Blake2Prefix;
use crate::rawbundle2::RawBundle2;
use crate::redaction_key_list::RedactionKeyList;
use crate::sharded_map::ShardedMapNode;
use crate::skeleton_manifest::SkeletonManifest;
use crate::thrift;
use crate::unode::FileUnode;
use crate::unode::ManifestUnode;
use crate::ThriftConvert;

// There is no NULL_HASH for typed hashes. Any places that need a null hash should use an
// Option type, or perhaps a list as desired.

/// A type, which can be parsed from a blobstore key,
/// and from which a blobstore key can be produced
/// (this is implemented by various handle types, where
/// blobstore key consists of two things: a hash
/// and a string, describing what the key refers to)
pub trait BlobstoreKey: FromStr<Err = anyhow::Error> {
    /// Return a key suitable for blobstore use.
    fn blobstore_key(&self) -> String;
    fn parse_blobstore_key(key: &str) -> Result<Self>;
}

pub trait IdContext {
    type Id;
    fn id_from_data(data: impl AsRef<[u8]>) -> Self::Id;
}

/// An identifier used throughout Mononoke.
pub trait MononokeId:
    BlobstoreKey + Loadable + ThriftConvert + Debug + Copy + Eq + Hash + Sync + Send + 'static
where
    <Self as Loadable>::Value: BlobstoreValue<Key = Self>,
{
    /// Return a stable hash fingerprint that can be used for sampling
    fn sampling_fingerprint(&self) -> u64;
}

/// An identifier for a changeset in Mononoke.
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash, Abomonation)]
#[derive(mysql::OptTryFromRowField)]
pub struct ChangesetId(Blake2);

/// An identifier for a changeset hash prefix in Mononoke.
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash, Abomonation)]
pub struct ChangesetIdPrefix(Blake2Prefix);

/// The type for resolving changesets by prefix of the hash
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum ChangesetIdsResolvedFromPrefix {
    /// Found single changeset
    Single(ChangesetId),
    /// Found several changesets within the limit provided
    Multiple(Vec<ChangesetId>),
    /// Found too many changesets exceeding the limit provided
    TooMany(Vec<ChangesetId>),
    /// Changeset was not found
    NoMatch,
}

impl ChangesetIdsResolvedFromPrefix {
    pub fn from_vec_and_limit(mut cs_ids: Vec<ChangesetId>, limit: usize) -> Self {
        match cs_ids.len() {
            0 => Self::NoMatch,
            1 => Self::Single(cs_ids[0]),
            l if l <= limit => Self::Multiple(cs_ids),
            _ => Self::TooMany({
                cs_ids.truncate(limit);
                cs_ids
            }),
        }
    }

    pub fn to_vec(self) -> Vec<ChangesetId> {
        match self {
            Self::Single(cs_id) => vec![cs_id],
            Self::Multiple(cs_ids) => cs_ids,
            Self::TooMany(cs_ids) => cs_ids,
            Self::NoMatch => vec![],
        }
    }
}

/// An identifier for file contents in Mononoke.
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
pub struct ContentId(Blake2);

/// An identifier for a chunk of a file's contents.
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
pub struct ContentChunkId(Blake2);

/// An identifier for mapping from ContentId to ContentMetadataV2
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
pub struct ContentMetadataV2Id(Blake2);

/// An identifier for raw bundle2 contents in Mononoke
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
pub struct RawBundle2Id(Blake2);

/// An identifier for a file unode
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
pub struct FileUnodeId(Blake2);

/// An identifier for a manifest unode
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
pub struct ManifestUnodeId(Blake2);

/// An identifier for a deleted manifest v2
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
pub struct DeletedManifestV2Id(Blake2);

/// An identifier for a sharded map node used in deleted manifest v2
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
pub struct ShardedMapNodeDMv2Id(Blake2);

/// An identifier for a sharded map node used in basename suffix manifest
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
pub struct ShardedMapNodeBSSMId(Blake2);

/// An identifier for basename suffix manifest
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
pub struct BasenameSuffixSkeletonManifestId(Blake2);

/// An identifier for an fsnode
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
pub struct FsnodeId(Blake2);

/// An identifier for a skeleton manifest
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
pub struct SkeletonManifestId(Blake2);

#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
pub struct FastlogBatchId(Blake2);

#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
pub struct BlameId(Blake2);

#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
pub struct RedactionKeyListId(Blake2);

pub struct Blake2HexVisitor;

impl<'de> serde::de::Visitor<'de> for Blake2HexVisitor {
    type Value = String;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("64 hex digits")
    }

    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
    where
        E: serde::de::Error,
    {
        Ok(value.to_string())
    }

    fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
    where
        E: serde::de::Error,
    {
        Ok(value)
    }
}

/// Implementations of typed hashes.
#[macro_export]
macro_rules! impl_typed_hash_no_context {
    {
        hash_type => $typed: ty,
        thrift_type => $thrift_typed: path,
        blobstore_key => $blobstore_key: expr,
    } => {
        impl $typed {
            pub const fn new(blake2: $crate::private::Blake2) -> Self {
                Self(blake2)
            }

            #[cfg(test)]
            pub(crate) fn from_byte_array(arr: [u8; 32]) -> Self {
                Self::new($crate::private::Blake2::from_byte_array(arr))
            }

            #[inline]
            pub fn from_bytes(bytes: impl AsRef<[u8]>) -> $crate::private::anyhow::Result<Self> {
                $crate::private::Blake2::from_bytes(bytes).map(Self::new)
            }

            #[inline]
            pub fn from_ascii_str(s: &$crate::private::AsciiStr) -> $crate::private::anyhow::Result<Self> {
                $crate::private::Blake2::from_ascii_str(s).map(Self::new)
            }

            pub fn blake2(&self) -> &$crate::private::Blake2 {
                &self.0
            }

            #[inline]
            pub fn to_hex(&self) -> $crate::private::AsciiString {
                self.0.to_hex()
            }

            pub fn to_brief(&self) -> $crate::private::AsciiString {
                self.to_hex().into_iter().take(8).collect()
            }

            pub fn from_thrift(h: $thrift_typed) -> $crate::private::anyhow::Result<Self> {
                $crate::ThriftConvert::from_thrift(h)
            }

            pub fn into_thrift(self) -> $thrift_typed {
                $crate::ThriftConvert::into_thrift(self)
            }
        }

        impl $crate::ThriftConvert for $typed {
            const NAME: &'static str = stringify!($typed);
            type Thrift = $thrift_typed;

            fn from_thrift(h: Self::Thrift) -> $crate::private::anyhow::Result<Self> {
                // This assumes that a null hash is never serialized. This should always be the
                // case.
                match h.0 {
                    $crate::private::thrift::IdType::Blake2(blake2) => Ok(Self::new($crate::private::Blake2::from_thrift(blake2)?)),
                    $crate::private::thrift::IdType::UnknownField(x) => $crate::private::anyhow::bail!($crate::private::ErrorKind::InvalidThrift(
                        stringify!($typed).into(),
                        format!("unknown id type field: {}", x)
                    )),
                }
            }
            fn into_thrift(self) -> Self::Thrift {
                $thrift_typed($crate::private::thrift::IdType::Blake2(self.0.into_thrift()))
            }
            // Ids are special, their bytes serialization is NOT the thrift bytes serialization
            // as that is an union. Instead, it is simply the serialization of their blake2.
            fn from_bytes(b: &$crate::private::Bytes) -> Result<Self> {
                Self::from_bytes(b)
            }
            fn into_bytes(self) -> $crate::private::Bytes {
                self.into()
            }
        }

        impl BlobstoreKey for $typed {
            #[inline]
            fn blobstore_key(&self) -> String {
                format!(concat!($blobstore_key, ".blake2.{}"), self.0)
            }

            fn parse_blobstore_key(key: &str) -> $crate::private::anyhow::Result<Self> {
                let prefix = concat!($blobstore_key, ".blake2.");
                match key.strip_prefix(prefix) {
                    None => $crate::private::anyhow::bail!("{} is not a blobstore key for {}", key, stringify!($typed)),
                    Some(suffix) => Self::from_str(suffix),
                }
            }
        }

        impl TryFrom<$crate::private::Bytes> for $typed {
            type Error = $crate::private::anyhow::Error;
            #[inline]
            fn try_from(b: $crate::private::Bytes) -> $crate::private::anyhow::Result<Self> {
                Self::from_bytes(b)
            }
        }

        impl From<$typed> for $crate::private::Bytes {
            fn from(b: $typed) -> Self {
                Self::copy_from_slice(b.as_ref())
            }
        }

        impl std::str::FromStr for $typed {
            type Err = $crate::private::anyhow::Error;
            #[inline]
            fn from_str(s: &str) -> $crate::private::anyhow::Result<Self> {
                $crate::private::Blake2::from_str(s).map(Self::new)
            }
        }

        impl From<$crate::private::Blake2> for $typed {
            fn from(h: $crate::private::Blake2) -> $typed {
                Self::new(h)
            }
        }

        impl<'a> From<&'a $crate::private::Blake2> for $typed {
            fn from(h: &'a $crate::private::Blake2) -> $typed {
                Self::new(*h)
            }
        }

        impl AsRef<[u8]> for $typed {
            fn as_ref(&self) -> &[u8] {
                self.0.as_ref()
            }
        }

        impl std::fmt::Display for $typed {
            fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
                std::fmt::Display::fmt(&self.0, fmt)
            }
        }

        impl $crate::private::Arbitrary for $typed {
            fn arbitrary(g: &mut $crate::private::Gen) -> Self {
                Self::new($crate::private::Blake2::arbitrary(g))
            }

            fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
                $crate::private::empty_shrinker()
            }
        }

        impl $crate::private::Serialize for $typed {
            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where
                S: $crate::private::Serializer,
            {
                serializer.serialize_str(self.to_hex().as_str())
            }
        }

        impl<'de> $crate::private::Deserialize<'de> for $typed {
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
            where
                D: $crate::private::Deserializer<'de>,
            {
                use std::str::FromStr;
                use std::result::Result::*;

                let hex = deserializer.deserialize_string($crate::private::Blake2HexVisitor)?;
                match $crate::private::Blake2::from_str(hex.as_str()) {
                    Ok(blake2) => Ok(Self::new(blake2)),
                    Err(error) => Err($crate::private::DeError::custom(error)),
                }
            }
        }

    }
}

#[macro_export]
macro_rules! impl_typed_hash_loadable {
    {
        hash_type => $typed: ident,
        value_type => $value_type: ty,
    } => {
        #[async_trait]
        impl Loadable for $typed
        {
            type Value = $value_type;

            async fn load<'a, B: Blobstore>(
                &'a self,
                ctx: &'a CoreContext,
                blobstore: &'a B,
            ) -> Result<Self::Value, LoadableError> {
                let id = *self;
                let blobstore_key = id.blobstore_key();
                let get = blobstore.get(ctx, &blobstore_key);

                let bytes = get.await?.ok_or(LoadableError::Missing(blobstore_key))?;
                let blob: Blob<$typed> = Blob::new(id, bytes.into_raw_bytes());
                <Self::Value as BlobstoreValue>::from_blob(blob).map_err(LoadableError::Error)
            }
        }

    }
}

#[macro_export]
macro_rules! impl_typed_context {
    {
        hash_type => $typed: ident,
        context_type => $typed_context: ident,
        context_key => $key: expr,
    } => {
        /// Context for incrementally computing a hash.
        #[derive(Clone)]
        pub struct $typed_context($crate::hash::Context);

        impl $typed_context {
            /// Construct a context.
            #[inline]
            pub fn new() -> Self {
                $typed_context($crate::hash::Context::new($key.as_bytes()))
            }

            #[inline]
            pub fn update<T>(&mut self, data: T)
            where
                T: AsRef<[u8]>,
            {
                self.0.update(data)
            }

            #[inline]
            pub fn finish(self) -> $typed {
                $typed(self.0.finish())
            }
        }

        impl $crate::typed_hash::IdContext for $typed_context {
            type Id = $typed;
            fn id_from_data(data: impl AsRef<[u8]>) -> $typed {
                let mut context = $typed_context::new();
                context.update(data);
                context.finish()
            }
        }
    }
}

#[macro_export]
macro_rules! impl_typed_hash {
    {
        hash_type => $typed: ident,
        thrift_hash_type => $thrift_hash_type: path,
        value_type => $value_type: ty,
        context_type => $typed_context: ident,
        context_key => $key: expr,
    } => {
        $crate::impl_typed_hash_no_context! {
            hash_type => $typed,
            thrift_type => $thrift_hash_type,
            blobstore_key => $key,
        }

        $crate::impl_typed_hash_loadable! {
            hash_type => $typed,
            value_type => $value_type,
        }

        $crate::impl_typed_context! {
            hash_type => $typed,
            context_type => $typed_context,
            context_key => $key,
        }

        impl MononokeId for $typed {
            #[inline]
            fn sampling_fingerprint(&self) -> u64 {
                self.0.sampling_fingerprint()
            }
        }

    }
}

macro_rules! impl_edenapi_hash_convert {
    ($this: ident, $edenapi: ident) => {
        impl From<$this> for $edenapi {
            fn from(v: $this) -> Self {
                $edenapi::from(v.0.into_inner())
            }
        }

        impl From<$edenapi> for $this {
            fn from(v: $edenapi) -> Self {
                $this::new(Blake2::from_byte_array(v.into()))
            }
        }
    };
}

impl_typed_hash! {
    hash_type => ChangesetId,
    thrift_hash_type => thrift::ChangesetId,
    value_type => BonsaiChangeset,
    context_type => ChangesetIdContext,
    context_key => "changeset",
}

impl_edenapi_hash_convert!(ChangesetId, EdenapiBonsaiChangesetId);

impl_typed_hash! {
    hash_type => ContentId,
    thrift_hash_type => thrift::ContentId,
    value_type => FileContents,
    context_type => ContentIdContext,
    context_key => "content",
}

impl_edenapi_hash_convert!(ContentId, EdenapiContentId);

impl_typed_hash! {
    hash_type => ContentChunkId,
    thrift_hash_type => thrift::ContentChunkId,
    value_type => ContentChunk,
    context_type => ContentChunkIdContext,
    context_key => "chunk",
}

impl_typed_hash! {
    hash_type => RawBundle2Id,
    thrift_hash_type => thrift::RawBundle2Id,
    value_type => RawBundle2,
    context_type => RawBundle2IdContext,
    context_key => "rawbundle2",
}

impl_typed_hash! {
    hash_type => FileUnodeId,
    thrift_hash_type => thrift::FileUnodeId,
    value_type => FileUnode,
    context_type => FileUnodeIdContext,
    context_key => "fileunode",
}

impl_typed_hash! {
    hash_type => ManifestUnodeId,
    thrift_hash_type => thrift::ManifestUnodeId,
    value_type => ManifestUnode,
    context_type => ManifestUnodeIdContext,
    context_key => "manifestunode",
}

impl_typed_hash! {
    hash_type => DeletedManifestV2Id,
    thrift_hash_type => thrift::DeletedManifestV2Id,
    value_type => DeletedManifestV2,
    context_type => DeletedManifestV2Context,
    context_key => "deletedmanifest2",
}

impl_typed_hash! {
    hash_type => ShardedMapNodeDMv2Id,
    thrift_hash_type => thrift::ShardedMapNodeId,
    value_type => ShardedMapNode<DeletedManifestV2Id>,
    context_type => ShardedMapNodeDMv2Context,
    context_key => "deletedmanifest2.mapnode",
}

impl_typed_hash! {
    hash_type => BasenameSuffixSkeletonManifestId,
    thrift_hash_type => thrift::BasenameSuffixSkeletonManifestId,
    value_type => BasenameSuffixSkeletonManifest,
    context_type => BasenameSuffixSkeletonManifestContext,
    context_key => "bssm",
}

impl_typed_hash! {
    hash_type => ShardedMapNodeBSSMId,
    thrift_hash_type => thrift::ShardedMapNodeId,
    value_type => ShardedMapNode<BssmEntry>,
    context_type => ShardedMapNodeBSSMContext,
    context_key => "bssm.mapnode",
}

impl_typed_hash! {
    hash_type => FsnodeId,
    thrift_hash_type => thrift::FsnodeId,
    value_type => Fsnode,
    context_type => FsnodeIdContext,
    context_key => "fsnode",
}

impl_typed_hash! {
    hash_type => RedactionKeyListId,
    thrift_hash_type => thrift::RedactionKeyListId,
    value_type => RedactionKeyList,
    context_type => RedactionKeyListIdContext,
    context_key => "redactionkeylist",
}

impl_edenapi_hash_convert!(FsnodeId, EdenapiFsnodeId);

impl_typed_hash! {
    hash_type => SkeletonManifestId,
    thrift_hash_type => thrift::SkeletonManifestId,
    value_type => SkeletonManifest,
    context_type => SkeletonManifestIdContext,
    context_key => "skeletonmanifest",
}

impl_typed_hash_no_context! {
    hash_type => ContentMetadataV2Id,
    thrift_type => thrift::ContentMetadataV2Id,
    blobstore_key => "content_metadata2",
}

impl_typed_hash_loadable! {
    hash_type => ContentMetadataV2Id,
    value_type => ContentMetadataV2,
}

impl_typed_hash! {
    hash_type => FastlogBatchId,
    thrift_hash_type => thrift::FastlogBatchId,
    value_type => FastlogBatch,
    context_type => FastlogBatchIdContext,
    context_key => "fastlogbatch",
}

impl From<ContentId> for ContentMetadataV2Id {
    fn from(content_id: ContentId) -> Self {
        Self(content_id.0)
    }
}

impl MononokeId for ContentMetadataV2Id {
    #[inline]
    fn sampling_fingerprint(&self) -> u64 {
        self.0.sampling_fingerprint()
    }
}

impl ChangesetIdPrefix {
    pub const fn new(blake2prefix: Blake2Prefix) -> Self {
        ChangesetIdPrefix(blake2prefix)
    }

    pub fn from_bytes<B: AsRef<[u8]> + ?Sized>(bytes: &B) -> Result<Self> {
        Blake2Prefix::from_bytes(bytes).map(Self::new)
    }

    #[inline]
    pub fn min_as_ref(&self) -> &[u8] {
        self.0.min_as_ref()
    }

    #[inline]
    pub fn max_as_ref(&self) -> &[u8] {
        self.0.max_as_ref()
    }

    #[inline]
    pub fn min_bound(&self) -> ChangesetId {
        ChangesetId(self.0.min_bound())
    }

    #[inline]
    pub fn max_bound(&self) -> ChangesetId {
        ChangesetId(self.0.max_bound())
    }

    #[inline]
    pub fn into_changeset_id(self) -> Option<ChangesetId> {
        self.0.into_blake2().map(ChangesetId)
    }
}

impl FromStr for ChangesetIdPrefix {
    type Err = <Blake2Prefix as FromStr>::Err;
    fn from_str(s: &str) -> result::Result<ChangesetIdPrefix, Self::Err> {
        Blake2Prefix::from_str(s).map(ChangesetIdPrefix)
    }
}

impl Display for ChangesetIdPrefix {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        Display::fmt(&self.0, fmt)
    }
}

impl From<ChangesetId> for EdenapiCommitId {
    fn from(value: ChangesetId) -> Self {
        EdenapiCommitId::Bonsai(value.into())
    }
}

#[cfg(test)]
mod test {
    use bytes::Bytes;
    use quickcheck::quickcheck;

    use super::*;

    quickcheck! {
        fn changesetid_thrift_roundtrip(h: ChangesetId) -> bool {
            let v = h.into_thrift();
            let sh = ChangesetId::from_thrift(v)
                .expect("converting a valid Thrift structure should always work");
            h == sh
        }

        fn contentid_thrift_roundtrip(h: ContentId) -> bool {
            let v = h.into_thrift();
            let sh = ContentId::from_thrift(v)
                .expect("converting a valid Thrift structure should always work");
            h == sh
        }
    }

    #[test]
    fn thrift_convert_bytes_consistent_for_ids() {
        let id = ShardedMapNodeDMv2Id::from_byte_array([1; 32]);
        let bytes_1 = Bytes::from(id.clone());
        let bytes_2 = ThriftConvert::into_bytes(id);
        assert_eq!(bytes_1, bytes_2);
        let rev_id_1_2: ShardedMapNodeDMv2Id = ThriftConvert::from_bytes(&bytes_1).unwrap();
        let rev_id_1_1 = ShardedMapNodeDMv2Id::from_bytes(bytes_1).unwrap();
        let rev_id_2_2: ShardedMapNodeDMv2Id = ThriftConvert::from_bytes(&bytes_2).unwrap();
        let rev_id_2_1 = ShardedMapNodeDMv2Id::from_bytes(bytes_2).unwrap();
        assert_eq!(rev_id_1_2, rev_id_1_1);
        assert_eq!(rev_id_1_1, rev_id_2_2);
        assert_eq!(rev_id_2_2, rev_id_2_1);
    }

    #[test]
    fn blobstore_key() {
        // These IDs are persistent, and this test is really to make sure that they don't change
        // accidentally.
        let id = ChangesetId::new(Blake2::from_byte_array([1; 32]));
        assert_eq!(id.blobstore_key(), format!("changeset.blake2.{}", id));

        let id = ContentId::new(Blake2::from_byte_array([1; 32]));
        assert_eq!(id.blobstore_key(), format!("content.blake2.{}", id));

        let id = ShardedMapNodeDMv2Id::from_byte_array([1; 32]);
        assert_eq!(
            id.blobstore_key(),
            format!("deletedmanifest2.mapnode.blake2.{}", id)
        );

        let id = ShardedMapNodeBSSMId::from_byte_array([1; 32]);
        assert_eq!(id.blobstore_key(), format!("bssm.mapnode.blake2.{}", id));

        let id = ContentChunkId::from_byte_array([1; 32]);
        assert_eq!(id.blobstore_key(), format!("chunk.blake2.{}", id));

        let id = RawBundle2Id::from_byte_array([1; 32]);
        assert_eq!(id.blobstore_key(), format!("rawbundle2.blake2.{}", id));

        let id = FileUnodeId::from_byte_array([1; 32]);
        assert_eq!(id.blobstore_key(), format!("fileunode.blake2.{}", id));

        let id = ManifestUnodeId::from_byte_array([1; 32]);
        assert_eq!(id.blobstore_key(), format!("manifestunode.blake2.{}", id));

        let id = DeletedManifestV2Id::from_byte_array([1; 32]);
        assert_eq!(
            id.blobstore_key(),
            format!("deletedmanifest2.blake2.{}", id)
        );

        let id = BasenameSuffixSkeletonManifestId::from_byte_array([1; 32]);
        assert_eq!(id.blobstore_key(), format!("bssm.blake2.{}", id),);

        let id = FsnodeId::from_byte_array([1; 32]);
        assert_eq!(id.blobstore_key(), format!("fsnode.blake2.{}", id));

        let id = SkeletonManifestId::from_byte_array([1; 32]);
        assert_eq!(
            id.blobstore_key(),
            format!("skeletonmanifest.blake2.{}", id)
        );

        let id = ContentMetadataV2Id::from_byte_array([1; 32]);
        assert_eq!(
            id.blobstore_key(),
            format!("content_metadata2.blake2.{}", id)
        );

        let id = FastlogBatchId::from_byte_array([1; 32]);
        assert_eq!(id.blobstore_key(), format!("fastlogbatch.blake2.{}", id));

        let id = RedactionKeyListId::from_byte_array([1; 32]);
        assert_eq!(
            id.blobstore_key(),
            format!("redactionkeylist.blake2.{}", id)
        );
    }

    #[test]
    fn test_serialize_deserialize() {
        let id = ChangesetId::new(Blake2::from_byte_array([1; 32]));
        let serialized = serde_json::to_string(&id).unwrap();
        let deserialized = serde_json::from_str(&serialized).unwrap();
        assert_eq!(id, deserialized);

        let id = ContentId::new(Blake2::from_byte_array([1; 32]));
        let serialized = serde_json::to_string(&id).unwrap();
        let deserialized = serde_json::from_str(&serialized).unwrap();
        assert_eq!(id, deserialized);

        let id = ShardedMapNodeDMv2Id::from_byte_array([1; 32]);
        let serialized = serde_json::to_string(&id).unwrap();
        let deserialized = serde_json::from_str(&serialized).unwrap();
        assert_eq!(id, deserialized);

        let id = ShardedMapNodeBSSMId::from_byte_array([1; 32]);
        let serialized = serde_json::to_string(&id).unwrap();
        let deserialized = serde_json::from_str(&serialized).unwrap();
        assert_eq!(id, deserialized);

        let id = ContentChunkId::from_byte_array([1; 32]);
        let serialized = serde_json::to_string(&id).unwrap();
        let deserialized = serde_json::from_str(&serialized).unwrap();
        assert_eq!(id, deserialized);

        let id = RawBundle2Id::from_byte_array([1; 32]);
        let serialized = serde_json::to_string(&id).unwrap();
        let deserialized = serde_json::from_str(&serialized).unwrap();
        assert_eq!(id, deserialized);

        let id = FileUnodeId::from_byte_array([1; 32]);
        let serialized = serde_json::to_string(&id).unwrap();
        let deserialized = serde_json::from_str(&serialized).unwrap();
        assert_eq!(id, deserialized);

        let id = ManifestUnodeId::from_byte_array([1; 32]);
        let serialized = serde_json::to_string(&id).unwrap();
        let deserialized = serde_json::from_str(&serialized).unwrap();
        assert_eq!(id, deserialized);

        let id = DeletedManifestV2Id::from_byte_array([1; 32]);
        let serialized = serde_json::to_string(&id).unwrap();
        let deserialized = serde_json::from_str(&serialized).unwrap();
        assert_eq!(id, deserialized);

        let id = BasenameSuffixSkeletonManifestId::from_byte_array([1; 32]);
        let serialized = serde_json::to_string(&id).unwrap();
        let deserialized = serde_json::from_str(&serialized).unwrap();
        assert_eq!(id, deserialized);

        let id = FsnodeId::from_byte_array([1; 32]);
        let serialized = serde_json::to_string(&id).unwrap();
        let deserialized = serde_json::from_str(&serialized).unwrap();
        assert_eq!(id, deserialized);

        let id = SkeletonManifestId::from_byte_array([1; 32]);
        let serialized = serde_json::to_string(&id).unwrap();
        let deserialized = serde_json::from_str(&serialized).unwrap();
        assert_eq!(id, deserialized);

        let id = ContentMetadataV2Id::from_byte_array([1; 32]);
        let serialized = serde_json::to_string(&id).unwrap();
        let deserialized = serde_json::from_str(&serialized).unwrap();
        assert_eq!(id, deserialized);

        let id = FastlogBatchId::from_byte_array([1; 32]);
        let serialized = serde_json::to_string(&id).unwrap();
        let deserialized = serde_json::from_str(&serialized).unwrap();
        assert_eq!(id, deserialized);

        let id = RedactionKeyListId::from_byte_array([1; 32]);
        let serialized = serde_json::to_string(&id).unwrap();
        let deserialized = serde_json::from_str(&serialized).unwrap();
        assert_eq!(id, deserialized);
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! Mutable counters maintains numeric counters for each Mononoke repository.
//! These are used to maintain simple state about each repo, for example which
//! revisions have been blobimported, replayed, etc.
//!
//! The counter values themselves are stored in a table in the metadata
//! database.

use anyhow::Result;
use async_trait::async_trait;
use context::CoreContext;
use context::PerfCounterType;
use mononoke_types::RepositoryId;
use sql::Transaction as SqlTransaction;
use sql_construct::SqlConstruct;
use sql_construct::SqlConstructFromMetadataDatabaseConfig;
use sql_ext::mononoke_queries;
use sql_ext::SqlConnections;
use sql_ext::TransactionResult;
use stats::prelude::*;

define_stats! {
    prefix = "mononoke.mutable_counters";
    cur_value: dynamic_singleton_counter("{}.cur_value", (name: String)),
}

#[facet::facet]
#[async_trait]
pub trait MutableCounters {
    /// Get the current value of the counter
    async fn get_counter(&self, ctx: &CoreContext, name: &str) -> Result<Option<i64>>;

    async fn get_maybe_stale_counter(&self, ctx: &CoreContext, name: &str) -> Result<Option<i64>>;

    /// Set the current value of the counter. if `prev_value` is not None,
    /// then the value is only updated if the previous value matches.
    async fn set_counter(
        &self,
        ctx: &CoreContext,
        name: &str,
        value: i64,
        prev_value: Option<i64>,
    ) -> Result<bool>;

    /// Get the names and values of all the counters for the repository.
    async fn get_all_counters(&self, ctx: &CoreContext) -> Result<Vec<(String, i64)>>;
}

mononoke_queries! {
    write SetCounter(
        repo_id: RepositoryId, name: &str, value: i64
    ) {
        none,
        mysql(
            "REPLACE INTO mutable_counters (repo_id, name, value) VALUES ({repo_id}, {name}, {value})"
        )
        sqlite(
            "REPLACE INTO mutable_counters (repo_id, name, value) VALUES ({repo_id}, CAST({name} AS TEXT), {value})"
        )
    }

    write SetCounterConditionally(
        repo_id: RepositoryId, name: &str, value: i64, prev_value: i64
    ) {
        none,
        mysql(
            "UPDATE mutable_counters SET value = {value}
            WHERE repo_id = {repo_id} AND name = {name} AND value = {prev_value}"
        )
        sqlite(
            "UPDATE mutable_counters SET value = {value}
            WHERE repo_id = {repo_id} AND name = CAST({name} AS TEXT) AND value = {prev_value}"
        )
    }

    read GetCounter(repo_id: RepositoryId, name: &str) -> (i64) {
        mysql(
            "SELECT value FROM mutable_counters WHERE repo_id = {repo_id} and name = {name}"
        )
        sqlite(
            "SELECT value FROM mutable_counters WHERE repo_id = {repo_id} and name = CAST({name} AS TEXT)"
        )
    }

    read GetCountersForRepo(repo_id: RepositoryId) -> (String, i64) {
        "SELECT name, value FROM mutable_counters WHERE repo_id = {repo_id} ORDER BY name"
    }
}

pub struct SqlMutableCounters {
    repo_id: RepositoryId,
    connections: SqlConnections,
}

pub struct SqlMutableCountersBuilder {
    connections: SqlConnections,
}

impl SqlConstruct for SqlMutableCountersBuilder {
    const LABEL: &'static str = "mutable_counters";

    const CREATION_QUERY: &'static str = include_str!("../schemas/sqlite-mutable-counters.sql");

    fn from_sql_connections(connections: SqlConnections) -> Self {
        Self { connections }
    }
}

impl SqlConstructFromMetadataDatabaseConfig for SqlMutableCountersBuilder {}

impl SqlMutableCountersBuilder {
    pub fn build(self, repo_id: RepositoryId) -> SqlMutableCounters {
        SqlMutableCounters {
            repo_id,
            connections: self.connections,
        }
    }
}

#[async_trait]
impl MutableCounters for SqlMutableCounters {
    async fn get_counter(&self, ctx: &CoreContext, name: &str) -> Result<Option<i64>> {
        ctx.perf_counters()
            .increment_counter(PerfCounterType::SqlReadsMaster);
        let conn = &self.connections.read_master_connection;
        let counter = GetCounter::query(conn, &self.repo_id, &name).await?;
        Ok(counter.first().map(|entry| entry.0))
    }

    async fn get_maybe_stale_counter(&self, ctx: &CoreContext, name: &str) -> Result<Option<i64>> {
        ctx.perf_counters()
            .increment_counter(PerfCounterType::SqlReadsReplica);
        let conn = &self.connections.read_connection;
        let counter = GetCounter::query(conn, &self.repo_id, &name).await?;
        Ok(counter.first().map(|entry| entry.0))
    }

    async fn set_counter(
        &self,
        ctx: &CoreContext,
        name: &str,
        value: i64,
        prev_value: Option<i64>,
    ) -> Result<bool> {
        let conn = &self.connections.write_connection;
        let txn = conn.start_transaction().await?;
        let txn_result =
            Self::set_counter_on_txn(ctx, self.repo_id, name, value, prev_value, txn).await?;
        match txn_result {
            TransactionResult::Succeeded(txn) => {
                txn.commit().await?;
                STATS::cur_value.set_value(ctx.fb, value, (name.to_owned(),));
                Ok(true)
            }
            TransactionResult::Failed => Ok(false),
        }
    }

    async fn get_all_counters(&self, ctx: &CoreContext) -> Result<Vec<(String, i64)>> {
        ctx.perf_counters()
            .increment_counter(PerfCounterType::SqlReadsMaster);
        let conn = &self.connections.read_master_connection;
        let counters = GetCountersForRepo::query(conn, &self.repo_id).await?;
        Ok(counters.into_iter().collect())
    }
}

impl SqlMutableCounters {
    pub async fn set_counter_on_txn(
        ctx: &CoreContext,
        repo_id: RepositoryId,
        name: &str,
        value: i64,
        prev_value: Option<i64>,
        txn: SqlTransaction,
    ) -> Result<TransactionResult> {
        ctx.perf_counters()
            .increment_counter(PerfCounterType::SqlWrites);
        let (txn, result) = if let Some(prev_value) = prev_value {
            SetCounterConditionally::query_with_transaction(
                txn,
                &repo_id,
                &name,
                &value,
                &prev_value,
            )
            .await?
        } else {
            SetCounter::query_with_transaction(txn, &repo_id, &name, &value).await?
        };

        Ok(if result.affected_rows() >= 1 {
            TransactionResult::Succeeded(txn)
        } else {
            TransactionResult::Failed
        })
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use anyhow::Result;
use context::CoreContext;
use fbinit::FacebookInit;
use mononoke_types_mocks::repo::REPO_ZERO;
use mutable_counters::MutableCounters;
use mutable_counters::SqlMutableCounters;
use mutable_counters::SqlMutableCountersBuilder;
use sql_construct::SqlConstruct;

fn create_db() -> Result<SqlMutableCounters> {
    Ok(SqlMutableCountersBuilder::with_sqlite_in_memory()?.build(REPO_ZERO))
}

#[fbinit::test]
async fn test_counter_simple(fb: FacebookInit) -> Result<()> {
    let ctx = CoreContext::test_mock(fb);
    let mutable_counters = create_db()?;

    mutable_counters
        .set_counter(&ctx, "counter", 1, None)
        .await?;
    assert_eq!(
        mutable_counters.get_counter(&ctx, "counter").await?,
        Some(1)
    );

    mutable_counters
        .set_counter(&ctx, "counter", 2, None)
        .await?;
    assert_eq!(
        mutable_counters.get_counter(&ctx, "counter").await?,
        Some(2)
    );

    Ok(())
}

#[fbinit::test]
async fn test_counter_conditional_update(fb: FacebookInit) -> Result<()> {
    let ctx = CoreContext::test_mock(fb);
    let mutable_counters = create_db()?;

    mutable_counters
        .set_counter(&ctx, "counter", 1, None)
        .await?;
    assert_eq!(
        mutable_counters.get_counter(&ctx, "counter").await?,
        Some(1)
    );

    assert!(
        mutable_counters
            .set_counter(&ctx, "counter", 2, Some(1))
            .await?
    );
    assert_eq!(
        mutable_counters.get_counter(&ctx, "counter").await?,
        Some(2)
    );

    // Not updated because prev_value is incorrect
    assert!(
        !mutable_counters
            .set_counter(&ctx, "counter", 3, Some(1))
            .await?
    );
    assert_eq!(
        mutable_counters.get_counter(&ctx, "counter").await?,
        Some(2)
    );

    // Attempt to update another counter, make sure it doesn't touch first counter
    // and update doesn't succeed as previous value is not set.
    assert!(
        !mutable_counters
            .set_counter(&ctx, "counter2", 3, Some(2))
            .await?,
    );
    assert_eq!(
        mutable_counters.get_counter(&ctx, "counter").await?,
        Some(2)
    );
    assert_eq!(mutable_counters.get_counter(&ctx, "counter2").await?, None);

    Ok(())
}


// @generated by autocargo
use std::env;
use std::fs;
use std::path::Path;

use thrift_compiler::Config;
use thrift_compiler::GenContext;

#[rustfmt::skip]
fn main() {
    // Rerun if this gets rewritten.
    println!("cargo:rerun-if-changed=thrift_build.rs");

    let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR env not provided");
    let out_dir: &Path = out_dir.as_ref();
    fs::write(
        out_dir.join("cratemap"),
        "mononoke_types_thrift mononoke_types_thrift
mutable_rename crate",
    ).expect("Failed to write cratemap");

    let conf = {
        let mut conf = Config::from_env(GenContext::Lib).expect("Failed to instantiate thrift_compiler::Config");

        let path_from_manifest_to_base: &Path = "../../../..".as_ref();
        let cargo_manifest_dir =
            env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not provided");
        let cargo_manifest_dir: &Path = cargo_manifest_dir.as_ref();
        let base_path = cargo_manifest_dir
            .join(path_from_manifest_to_base)
            .canonicalize()
            .expect("Failed to canonicalize base_path");
        // TODO: replace canonicalize() with std::path::absolute() when
        // https://github.com/rust-lang/rust/pull/91673 is available (~Rust 1.60)
        // and remove this block.
        #[cfg(windows)]
        let base_path = Path::new(
            base_path
                .as_path()
                .to_string_lossy()
                .trim_start_matches(r"\\?\"),
            )
            .to_path_buf();

        conf.base_path(base_path);

        let options = "types_crate=mutable-rename-thrift__types";
        if !options.is_empty() {
            conf.options(options);
        }

        let include_srcs = vec![

        ];
        conf.include_srcs(include_srcs);

        conf
    };

    conf
        .run(&[
            "mutable_rename.thrift"
        ])
        .expect("Failed while running thrift compilation");
}


// @generated by autocargo
::codegen_includer_proc_macro::include!();


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::collections::HashMap;
use std::collections::HashSet;
use std::time::Duration;

use abomonation_derive::Abomonation;
use anyhow::Context;
use anyhow::Error;
use async_trait::async_trait;
use bytes::Bytes;
use caching_ext::CacheDisposition;
use caching_ext::CacheHandlerFactory;
use caching_ext::CacheTtl;
use caching_ext::CachelibHandler;
use caching_ext::EntityStore;
use caching_ext::KeyedEntityStore;
use caching_ext::McErrorKind;
use caching_ext::McResult;
use caching_ext::MemcacheEntity;
use caching_ext::MemcacheHandler;
use context::CoreContext;
use fbthrift::compact_protocol;
use memcache::KeyGen;
use mononoke_types::hash::Blake2;
use mononoke_types::path_bytes_from_mpath;
use mononoke_types::ChangesetId;
use mononoke_types::MPath;
use mutable_rename_thrift as thrift;
use path_hash::PathHash;
use path_hash::PathHashBytes;

use crate::MutableRenameEntry;
use crate::MutableRenames;

/// Bump this when code changes the layout of memcache
pub const CODEVER: u32 = 0;

#[derive(Clone)]
pub struct CacheHandlers {
    memcache: MemcacheHandler,
    presence_cachelib: CachelibHandler<HasMutableRename>,
    presence_keygen: KeyGen,
    rename_cachelib: CachelibHandler<CachedMutableRenameEntry>,
    rename_keygen: KeyGen,
    get_cs_ids_cachelib: CachelibHandler<ChangesetIdSet>,
    get_cs_ids_keygen: KeyGen,
}

impl CacheHandlers {
    pub fn new(cache_handler_factory: CacheHandlerFactory) -> Result<Self, Error> {
        let sitever = tunables::tunables()
            .mutable_renames_sitever()
            .unwrap_or_default()
            .try_into()
            .context("While converting from i64 to u32 sitever")?;
        let presence_keygen = KeyGen::new("scm.mononoke.mutable_renames.present", CODEVER, sitever);
        let rename_keygen = KeyGen::new("scm.mononoke.mutable_renames.rename", CODEVER, sitever);
        let get_cs_ids_keygen = KeyGen::new(
            "scm.mononoke.mutable_renames.cs_ids_for_path",
            CODEVER,
            sitever,
        );
        Ok(Self {
            memcache: cache_handler_factory.memcache(),
            presence_cachelib: cache_handler_factory.cachelib(),
            presence_keygen,
            rename_cachelib: cache_handler_factory.cachelib(),
            rename_keygen,
            get_cs_ids_cachelib: cache_handler_factory.cachelib(),
            get_cs_ids_keygen,
        })
    }

    pub fn new_test() -> Self {
        Self::new(CacheHandlerFactory::Mocked)
            .expect("Test construction of CacheHandlers should succeed")
    }

    pub fn has_rename<'a>(
        &'a self,
        owner: &'a MutableRenames,
        ctx: &'a CoreContext,
    ) -> CachedHasMutableRename<'a> {
        let memcache = &self.memcache;
        let keygen = &self.presence_keygen;
        let cachelib = &self.presence_cachelib;
        CachedHasMutableRename {
            owner,
            cachelib,
            memcache,
            keygen,
            ctx,
        }
    }

    pub fn get_rename<'a>(
        &'a self,
        owner: &'a MutableRenames,
        ctx: &'a CoreContext,
    ) -> CachedGetMutableRename<'a> {
        let memcache = &self.memcache;
        let keygen = &self.rename_keygen;
        let cachelib = &self.rename_cachelib;
        CachedGetMutableRename {
            owner,
            cachelib,
            memcache,
            keygen,
            ctx,
        }
    }

    pub fn get_cs_ids_with_rename<'a>(
        &'a self,
        owner: &'a MutableRenames,
        ctx: &'a CoreContext,
    ) -> CachedGetCsIdsWithRename<'a> {
        let memcache = &self.memcache;
        let keygen = &self.get_cs_ids_keygen;
        let cachelib = &self.get_cs_ids_cachelib;
        CachedGetCsIdsWithRename {
            owner,
            cachelib,
            memcache,
            keygen,
            ctx,
        }
    }
}

#[derive(Abomonation, Clone, Copy, Debug)]
pub struct HasMutableRename(pub bool);
const TRUE: &[u8] = b"y";
const FALSE: &[u8] = b"n";

impl MemcacheEntity for HasMutableRename {
    fn serialize(&self) -> Bytes {
        if self.0 {
            Bytes::from_static(TRUE)
        } else {
            Bytes::from_static(FALSE)
        }
    }

    fn deserialize(bytes: Bytes) -> McResult<Self> {
        if bytes == TRUE {
            Ok(HasMutableRename(true))
        } else if bytes == FALSE {
            Ok(HasMutableRename(false))
        } else {
            Err(McErrorKind::Deserialization)
        }
    }
}

pub struct CachedHasMutableRename<'a> {
    owner: &'a MutableRenames,
    cachelib: &'a CachelibHandler<HasMutableRename>,
    memcache: &'a MemcacheHandler,
    keygen: &'a KeyGen,
    ctx: &'a CoreContext,
}

impl<'a> EntityStore<HasMutableRename> for CachedHasMutableRename<'a> {
    fn cachelib(&self) -> &CachelibHandler<HasMutableRename> {
        self.cachelib
    }

    fn keygen(&self) -> &KeyGen {
        self.keygen
    }

    fn memcache(&self) -> &MemcacheHandler {
        self.memcache
    }

    fn cache_determinator(&self, _v: &HasMutableRename) -> CacheDisposition {
        // A cache TTL of 4 hours means that worst case is 8 hours from making
        // a change to caches all showing it.
        //
        // Worst case is we fill memcache just before the change, giving us 4 hours
        // in memcache, then all tasks fill from memcache just before it expires,
        // giving us a further 4 hours (8 total) where all tasks have the stale data.
        CacheDisposition::Cache(CacheTtl::Ttl(Duration::from_secs(4 * 60 * 60)))
    }

    caching_ext::impl_singleton_stats!("mutable_renames.presence");
}

#[async_trait]
impl<'a> KeyedEntityStore<ChangesetId, HasMutableRename> for CachedHasMutableRename<'a> {
    fn get_cache_key(&self, key: &ChangesetId) -> String {
        format!(
            "mutable_renames.presence.repo{}.{}",
            self.owner.repo_id, *key
        )
    }

    async fn get_from_db(
        &self,
        keys: HashSet<ChangesetId>,
    ) -> Result<HashMap<ChangesetId, HasMutableRename>, Error> {
        let mut res = HashMap::new();
        for key in keys {
            let has_rename = HasMutableRename(self.owner.has_rename_uncached(self.ctx, key).await?);
            res.insert(key, has_rename);
        }
        Ok(res)
    }
}

#[derive(Abomonation, Clone)]
pub struct CachedMutableRenameEntry(pub Option<CacheableMutableRenameEntry>);

#[derive(Abomonation, Clone)]
pub struct CacheableMutableRenameEntry {
    dst_cs_id: ChangesetId,
    dst_path_hash: PathHash,
    src_cs_id: ChangesetId,
    src_path: Option<Vec<u8>>,
    src_path_hash: PathHash,
    src_unode: Blake2,
    is_tree: i8,
}

impl TryFrom<CacheableMutableRenameEntry> for MutableRenameEntry {
    type Error = Error;
    fn try_from(entry: CacheableMutableRenameEntry) -> Result<Self, Error> {
        let CacheableMutableRenameEntry {
            dst_cs_id,
            dst_path_hash,
            src_cs_id,
            src_path,
            src_path_hash,
            src_unode,
            is_tree,
        } = entry;
        let src_path = src_path.as_ref().map(MPath::new).transpose()?;

        Ok(Self {
            dst_cs_id,
            dst_path_hash,
            src_cs_id,
            src_path,
            src_path_hash,
            src_unode,
            is_tree,
        })
    }
}
impl From<MutableRenameEntry> for CacheableMutableRenameEntry {
    fn from(entry: MutableRenameEntry) -> Self {
        let MutableRenameEntry {
            dst_cs_id,
            dst_path_hash,
            src_cs_id,
            src_path,
            src_path_hash,
            src_unode,
            is_tree,
        } = entry;
        let src_path = src_path.as_ref().map(MPath::to_vec);

        Self {
            dst_cs_id,
            dst_path_hash,
            src_cs_id,
            src_path,
            src_path_hash,
            src_unode,
            is_tree,
        }
    }
}

fn path_hash_to_thrift(hash: &PathHash) -> thrift::PathHash {
    let path = hash.path_bytes.0.clone();
    let is_tree = hash.is_tree;

    thrift::PathHash { path, is_tree }
}

fn path_hash_from_thrift(hash: thrift::PathHash) -> Result<PathHash, Error> {
    let path = MPath::new_opt(hash.path)?;
    Ok(PathHash::from_path_and_is_tree(path.as_ref(), hash.is_tree))
}

impl MemcacheEntity for CachedMutableRenameEntry {
    fn serialize(&self) -> Bytes {
        // Turn self into a thrift::MutableRenameEntry
        let thrift_self = match &self.0 {
            None => thrift::CachedMutableRenameEntry { entry: None },
            Some(CacheableMutableRenameEntry {
                dst_cs_id,
                dst_path_hash,
                src_cs_id,
                src_path,
                src_path_hash,
                src_unode,
                is_tree,
            }) => {
                let dst_cs_id = dst_cs_id.into_thrift();
                let dst_path_hash = path_hash_to_thrift(dst_path_hash);
                let src_cs_id = src_cs_id.into_thrift();
                let src_path = src_path.clone();
                let src_path_hash = path_hash_to_thrift(src_path_hash);
                let src_unode = src_unode.into_thrift();
                let is_tree = *is_tree;
                let entry = Some(thrift::MutableRenameEntry {
                    dst_cs_id,
                    dst_path_hash,
                    src_cs_id,
                    src_path,
                    src_path_hash,
                    src_unode,
                    is_tree,
                });
                thrift::CachedMutableRenameEntry { entry }
            }
        };
        compact_protocol::serialize(&thrift_self)
    }
    fn deserialize(bytes: Bytes) -> McResult<Self> {
        if let thrift::CachedMutableRenameEntry {
            entry:
                Some(thrift::MutableRenameEntry {
                    dst_cs_id,
                    dst_path_hash,
                    src_cs_id,
                    src_path,
                    src_path_hash,
                    src_unode,
                    is_tree,
                }),
        } = compact_protocol::deserialize(bytes).map_err(|_| McErrorKind::Deserialization)?
        {
            let dst_cs_id =
                ChangesetId::from_thrift(dst_cs_id).map_err(|_| McErrorKind::Deserialization)?;
            let dst_path_hash =
                path_hash_from_thrift(dst_path_hash).map_err(|_| McErrorKind::Deserialization)?;
            let src_cs_id =
                ChangesetId::from_thrift(src_cs_id).map_err(|_| McErrorKind::Deserialization)?;
            let src_path_hash =
                path_hash_from_thrift(src_path_hash).map_err(|_| McErrorKind::Deserialization)?;
            let src_unode =
                Blake2::from_thrift(src_unode).map_err(|_| McErrorKind::Deserialization)?;
            let entry = CacheableMutableRenameEntry {
                dst_cs_id,
                dst_path_hash,
                src_cs_id,
                src_path,
                src_path_hash,
                src_unode,
                is_tree,
            };
            Ok(CachedMutableRenameEntry(Some(entry)))
        } else {
            Ok(CachedMutableRenameEntry(None))
        }
    }
}

pub struct CachedGetMutableRename<'a> {
    owner: &'a MutableRenames,
    cachelib: &'a CachelibHandler<CachedMutableRenameEntry>,
    memcache: &'a MemcacheHandler,
    keygen: &'a KeyGen,
    ctx: &'a CoreContext,
}

impl<'a> EntityStore<CachedMutableRenameEntry> for CachedGetMutableRename<'a> {
    fn cachelib(&self) -> &CachelibHandler<CachedMutableRenameEntry> {
        self.cachelib
    }

    fn keygen(&self) -> &KeyGen {
        self.keygen
    }

    fn memcache(&self) -> &MemcacheHandler {
        self.memcache
    }

    fn cache_determinator(&self, _v: &CachedMutableRenameEntry) -> CacheDisposition {
        CacheDisposition::Cache(CacheTtl::Ttl(Duration::from_secs(4 * 60 * 60)))
    }

    caching_ext::impl_singleton_stats!("mutable_renames.get_rename");
}

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct RenameKey {
    dst_cs_id: ChangesetId,
    dst_path: Option<MPath>,
    dst_path_hash: PathHashBytes,
}

impl RenameKey {
    pub fn new(dst_cs_id: ChangesetId, dst_path: Option<MPath>) -> Self {
        let dst_path_bytes = path_bytes_from_mpath(dst_path.as_ref());
        let dst_path_hash = PathHashBytes::new(&dst_path_bytes);

        Self {
            dst_cs_id,
            dst_path,
            dst_path_hash,
        }
    }
}

#[async_trait]
impl<'a> KeyedEntityStore<RenameKey, CachedMutableRenameEntry> for CachedGetMutableRename<'a> {
    fn get_cache_key(&self, key: &RenameKey) -> String {
        match &key.dst_path {
            None => format!(
                "mutable_renames.rename.cs_id_at_root.repo{}.{}",
                self.owner.repo_id, key.dst_cs_id
            ),
            Some(_) => format!(
                "mutable_renames.rename.cs_id_and_path.repo{}.{}.{}",
                self.owner.repo_id, key.dst_cs_id, key.dst_path_hash
            ),
        }
    }

    async fn get_from_db(
        &self,
        keys: HashSet<RenameKey>,
    ) -> Result<HashMap<RenameKey, CachedMutableRenameEntry>, Error> {
        let mut res = HashMap::new();
        // Right now, the only caller always asks for a single entry from the cache, so
        // this function is either not called, or called once.
        // If we build a batch interface, we should make this use it for batched fills
        for key in keys {
            let rename_entry = CachedMutableRenameEntry(
                self.owner
                    .get_rename_uncached(self.ctx, key.dst_cs_id, key.dst_path.clone())
                    .await?
                    .map(CacheableMutableRenameEntry::from),
            );
            res.insert(key, rename_entry);
        }
        Ok(res)
    }
}

#[derive(Abomonation, Clone)]
pub struct ChangesetIdSet {
    // You can't easily Abomonate HashSet, but you can Vec. Store as Vec, construct from HashSet only
    set: Vec<ChangesetId>,
}

impl ChangesetIdSet {
    pub fn new(ids: HashSet<ChangesetId>) -> Self {
        let set = ids.into_iter().collect();
        Self { set }
    }
}

impl From<ChangesetIdSet> for HashSet<ChangesetId> {
    fn from(set: ChangesetIdSet) -> Self {
        set.set.into_iter().collect()
    }
}

impl MemcacheEntity for ChangesetIdSet {
    fn serialize(&self) -> Bytes {
        let thrift_self = thrift::ChangesetIdSet {
            cs_ids: self.set.iter().map(|c| c.into_thrift()).collect(),
        };
        compact_protocol::serialize(&thrift_self)
    }

    fn deserialize(bytes: Bytes) -> McResult<Self> {
        let thrift::ChangesetIdSet { cs_ids } =
            compact_protocol::deserialize(bytes).map_err(|_| McErrorKind::Deserialization)?;
        Ok(Self {
            set: cs_ids
                .into_iter()
                .map(ChangesetId::from_thrift)
                .collect::<Result<Vec<_>, Error>>()
                .map_err(|_| McErrorKind::Deserialization)?,
        })
    }
}

pub struct CachedGetCsIdsWithRename<'a> {
    owner: &'a MutableRenames,
    cachelib: &'a CachelibHandler<ChangesetIdSet>,
    memcache: &'a MemcacheHandler,
    keygen: &'a KeyGen,
    ctx: &'a CoreContext,
}

impl<'a> EntityStore<ChangesetIdSet> for CachedGetCsIdsWithRename<'a> {
    fn cachelib(&self) -> &CachelibHandler<ChangesetIdSet> {
        self.cachelib
    }

    fn keygen(&self) -> &KeyGen {
        self.keygen
    }

    fn memcache(&self) -> &MemcacheHandler {
        self.memcache
    }

    fn cache_determinator(&self, _v: &ChangesetIdSet) -> CacheDisposition {
        CacheDisposition::Cache(CacheTtl::Ttl(Duration::from_secs(4 * 60 * 60)))
    }

    caching_ext::impl_singleton_stats!("mutable_renames.get_cs_ids_with_rename");
}

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct GetCsIdsKey {
    dst_path: Option<MPath>,
    dst_path_hash: PathHashBytes,
}

impl GetCsIdsKey {
    pub fn new(dst_path: Option<MPath>) -> Self {
        let dst_path_bytes = path_bytes_from_mpath(dst_path.as_ref());
        let dst_path_hash = PathHashBytes::new(&dst_path_bytes);

        Self {
            dst_path,
            dst_path_hash,
        }
    }
}

#[async_trait]
impl<'a> KeyedEntityStore<GetCsIdsKey, ChangesetIdSet> for CachedGetCsIdsWithRename<'a> {
    fn get_cache_key(&self, key: &GetCsIdsKey) -> String {
        match &key.dst_path {
            None => format!(
                "mutable_renames.csids_with_renames_at_root.repo{}",
                self.owner.repo_id
            ),
            Some(_) => format!(
                "mutable_renames.csids_with_renames_at_path.repo{}.{}",
                self.owner.repo_id, key.dst_path_hash
            ),
        }
    }

    async fn get_from_db(
        &self,
        keys: HashSet<GetCsIdsKey>,
    ) -> Result<HashMap<GetCsIdsKey, ChangesetIdSet>, Error> {
        let mut res = HashMap::new();
        // Right now, the only caller always asks for a single entry from the cache, so
        // this function is either not called, or called once.
        // If we build a batch interface, we should make this use it for batched fills
        for key in keys {
            let cs_ids = ChangesetIdSet::new(
                self.owner
                    .get_cs_ids_with_rename_uncached(self.ctx, key.dst_path.clone())
                    .await?,
            );
            res.insert(key, cs_ids);
        }
        Ok(res)
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::collections::HashSet;
use std::sync::Arc;

use anyhow::anyhow;
use anyhow::bail;
use anyhow::Error;
use caching_ext::CacheHandlerFactory;
use changesets::Changesets;
use context::CoreContext;
use context::PerfCounterType;
use futures::try_join;
use manifest::Entry;
use maplit::hashset;
use mononoke_types::hash::Blake2;
use mononoke_types::path_bytes_from_mpath;
use mononoke_types::ChangesetId;
use mononoke_types::FileUnodeId;
use mononoke_types::MPath;
use mononoke_types::ManifestUnodeId;
use mononoke_types::RepositoryId;
use path_hash::PathHash;
use path_hash::PathHashBytes;
use sql::Connection;
use sql_construct::SqlConstruct;
use sql_construct::SqlConstructFromMetadataDatabaseConfig;
use sql_ext::mononoke_queries;
use sql_ext::SqlConnections;

mod caching;
use crate::caching::CacheHandlers;
use crate::caching::GetCsIdsKey;
use crate::caching::RenameKey;
#[cfg(test)]
mod tests;

pub struct SqlMutableRenamesStore {
    write_connection: Connection,
    read_connection: Connection,
}

impl SqlConstruct for SqlMutableRenamesStore {
    const LABEL: &'static str = "mutable_renames";

    const CREATION_QUERY: &'static str = include_str!("../schemas/sqlite-mutable-renames.sql");

    fn from_sql_connections(connections: SqlConnections) -> Self {
        Self {
            write_connection: connections.write_connection,
            read_connection: connections.read_connection,
        }
    }
}

impl SqlConstructFromMetadataDatabaseConfig for SqlMutableRenamesStore {}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MutableRenameEntry {
    dst_cs_id: ChangesetId,
    dst_path_hash: PathHash,
    src_cs_id: ChangesetId,
    src_path: Option<MPath>,
    src_path_hash: PathHash,
    src_unode: Blake2,
    is_tree: i8,
}

impl MutableRenameEntry {
    /// Create a new entry to pass to `add`
    /// This says that dst_path in dst_cs_id is in fact the immediate child of src_path at src_cs_id
    /// The unode is needed to allow us to reconstruct unode history correctly.
    /// If either path is `None`, this represents the root of the repo
    pub fn new(
        dst_cs_id: ChangesetId,
        dst_path: Option<MPath>,
        src_cs_id: ChangesetId,
        src_path: Option<MPath>,
        src_unode: Entry<ManifestUnodeId, FileUnodeId>,
    ) -> Result<Self, Error> {
        let (src_unode, is_tree) = match src_unode {
            Entry::Tree(ref mf_unode_id) => (*mf_unode_id.blake2(), true),
            Entry::Leaf(ref leaf_unode_id) => (*leaf_unode_id.blake2(), false),
        };

        let dst_path_hash = PathHash::from_path_and_is_tree(dst_path.as_ref(), is_tree);
        let src_path_hash = PathHash::from_path_and_is_tree(src_path.as_ref(), is_tree);
        let is_tree = *dst_path_hash.sql_is_tree();

        Ok(Self {
            dst_cs_id,
            dst_path_hash,
            src_cs_id,
            src_path,
            src_path_hash,
            src_unode,
            is_tree,
        })
    }

    fn dst_path_hash(&self) -> &PathHash {
        &self.dst_path_hash
    }

    /// Get the source path for this entry, or None if the source
    /// is the repo root
    pub fn src_path(&self) -> Option<&MPath> {
        self.src_path.as_ref()
    }

    fn src_path_hash(&self) -> &PathHash {
        &self.src_path_hash
    }

    /// Get the source changeset ID for this entry
    pub fn src_cs_id(&self) -> ChangesetId {
        self.src_cs_id
    }

    /// Get the unode you would find by looking up src_path()
    /// in src_cs_id() - this is faster because it's pre-cached
    pub fn src_unode(&self) -> Entry<ManifestUnodeId, FileUnodeId> {
        if self.is_tree == 1 {
            Entry::Tree(ManifestUnodeId::new(self.src_unode))
        } else {
            Entry::Leaf(FileUnodeId::new(self.src_unode))
        }
    }
}

#[facet::facet]
#[derive(Clone)]
pub struct MutableRenames {
    repo_id: RepositoryId,
    store: Arc<SqlMutableRenamesStore>,
    cache_handlers: Option<CacheHandlers>,
}

impl MutableRenames {
    pub fn new(
        repo_id: RepositoryId,
        store: SqlMutableRenamesStore,
        cache_handler_factory: Option<CacheHandlerFactory>,
    ) -> Result<Self, Error> {
        let cache_handlers = cache_handler_factory.map(CacheHandlers::new).transpose()?;
        Ok(Self {
            repo_id,
            store: Arc::new(store),
            cache_handlers,
        })
    }

    pub fn new_test(repo_id: RepositoryId, store: SqlMutableRenamesStore) -> Self {
        let cache_handlers = Some(CacheHandlers::new_test());
        Self {
            repo_id,
            store: Arc::new(store),
            cache_handlers,
        }
    }

    pub async fn add_or_overwrite_renames(
        &self,
        ctx: &CoreContext,
        changesets: &dyn Changesets,
        renames: Vec<MutableRenameEntry>,
    ) -> Result<(), Error> {
        ctx.perf_counters()
            .increment_counter(PerfCounterType::SqlWrites);

        // Check to see if any of the added renames has an src that's a
        // descendant of its dst. If so, we reject this as we cannot sanely
        // handle cycles in history
        for (src, dst) in renames.iter().map(|mre| (mre.src_cs_id, mre.dst_cs_id)) {
            let (src_entry, dst_entry) =
                try_join!(changesets.get(ctx, src), changesets.get(ctx, dst))?;
            let src_entry = src_entry.ok_or_else(|| anyhow!("Commit {} does not exist", src))?;
            let dst_entry = dst_entry.ok_or_else(|| anyhow!("Commit {} does not exist", dst))?;
            if src_entry.gen >= dst_entry.gen {
                // The source commit could potentially be a descendant of the target
                // Ideally, we'd do a proper check here to see if this forms a loop
                // in history, allowing for both mutable and immutable history
                //
                // For now, though, just bail
                bail!(
                    "{} is a potential descendant of {} - rejecting to avoid loops in history",
                    src_entry.cs_id,
                    dst_entry.cs_id
                );
            }
        }

        // First insert path <-> path_hash mapping
        let mut rows = vec![];
        for rename in &renames {
            rows.push((
                &rename.dst_path_hash().hash.0,
                &rename.dst_path_hash().path_bytes.0,
            ));
            rows.push((
                &rename.src_path_hash().hash.0,
                &rename.src_path_hash().path_bytes.0,
            ));
        }

        AddPaths::query(&self.store.write_connection, &rows[..]).await?;

        // Now insert the renames
        ctx.perf_counters()
            .increment_counter(PerfCounterType::SqlWrites);
        let mut rows = vec![];

        for rename in &renames {
            rows.push((
                &self.repo_id,
                &rename.dst_cs_id,
                &rename.dst_path_hash().hash.0,
                &rename.src_cs_id,
                &rename.src_path_hash().hash.0,
                &rename.src_unode,
                &rename.is_tree,
            ));
        }

        AddRenames::query(&self.store.write_connection, &rows[..]).await?;

        Ok(())
    }

    pub async fn has_rename_uncached(
        &self,
        ctx: &CoreContext,
        dst_cs_id: ChangesetId,
    ) -> Result<bool, Error> {
        ctx.perf_counters()
            .increment_counter(PerfCounterType::SqlReadsReplica);

        let rename_targets =
            HasRenameCheck::query(&self.store.read_connection, &self.repo_id, &dst_cs_id).await?;

        Ok(!rename_targets.is_empty())
    }

    pub async fn has_rename(
        &self,
        ctx: &CoreContext,
        dst_cs_id: ChangesetId,
    ) -> Result<bool, Error> {
        match &self.cache_handlers {
            None => self.has_rename_uncached(ctx, dst_cs_id).await,
            Some(cache_handlers) => {
                let keys = hashset![dst_cs_id];

                let cache = cache_handlers.has_rename(self, ctx);

                let res = caching_ext::get_or_fill(&cache, keys).await?;

                Ok(res.get(&dst_cs_id).map_or(false, |r| r.0))
            }
        }
    }

    pub async fn get_rename_uncached(
        &self,
        ctx: &CoreContext,
        dst_cs_id: ChangesetId,
        dst_path: Option<MPath>,
    ) -> Result<Option<MutableRenameEntry>, Error> {
        ctx.perf_counters()
            .increment_counter(PerfCounterType::SqlReadsReplica);

        if !self.has_rename_uncached(ctx, dst_cs_id).await? {
            return Ok(None);
        }

        let dst_path_bytes = path_bytes_from_mpath(dst_path.as_ref());
        let dst_path_hash = PathHashBytes::new(&dst_path_bytes);
        let mut rows = GetRename::query(
            &self.store.read_connection,
            &self.repo_id,
            &dst_cs_id,
            &dst_path_hash.0,
        )
        .await?;
        match rows.pop() {
            Some((src_cs_id, src_path_bytes, src_unode, is_tree)) => {
                let src_path = MPath::new_opt(src_path_bytes)?;
                let src_unode = if is_tree == 1 {
                    Entry::Tree(ManifestUnodeId::new(src_unode))
                } else {
                    Entry::Leaf(FileUnodeId::new(src_unode))
                };

                Ok(Some(MutableRenameEntry::new(
                    dst_cs_id, dst_path, src_cs_id, src_path, src_unode,
                )?))
            }
            None => Ok(None),
        }
    }

    pub async fn get_rename(
        &self,
        ctx: &CoreContext,
        dst_cs_id: ChangesetId,
        dst_path: Option<MPath>,
    ) -> Result<Option<MutableRenameEntry>, Error> {
        match &self.cache_handlers {
            None => self.get_rename_uncached(ctx, dst_cs_id, dst_path).await,
            Some(cache_handlers) => {
                let key = RenameKey::new(dst_cs_id, dst_path);
                let keys = hashset![key.clone()];

                let cache = cache_handlers.get_rename(self, ctx);

                let mut res = caching_ext::get_or_fill(&cache, keys).await?;

                let res = res
                    .remove(&key)
                    .and_then(|r| r.0.map(MutableRenameEntry::try_from))
                    .transpose()?;
                Ok(res)
            }
        }
    }

    pub async fn get_cs_ids_with_rename_uncached(
        &self,
        ctx: &CoreContext,
        dst_path: Option<MPath>,
    ) -> Result<HashSet<ChangesetId>, Error> {
        ctx.perf_counters()
            .increment_counter(PerfCounterType::SqlReadsReplica);

        let dst_path_bytes = path_bytes_from_mpath(dst_path.as_ref());
        let dst_path_hash = PathHashBytes::new(&dst_path_bytes);
        let rows = FindRenames::query(&self.store.read_connection, &self.repo_id, &dst_path_hash.0)
            .await?;
        Ok(rows.into_iter().map(|r| r.0).collect())
    }

    pub async fn get_cs_ids_with_rename(
        &self,
        ctx: &CoreContext,
        dst_path: Option<MPath>,
    ) -> Result<HashSet<ChangesetId>, Error> {
        match &self.cache_handlers {
            None => self.get_cs_ids_with_rename_uncached(ctx, dst_path).await,
            Some(cache_handlers) => {
                let key = GetCsIdsKey::new(dst_path);
                let keys = hashset![key.clone()];

                let cache = cache_handlers.get_cs_ids_with_rename(self, ctx);

                caching_ext::get_or_fill(&cache, keys).await.map(|mut r| {
                    let res = r.remove(&key);
                    res.map_or(HashSet::new(), |r| r.into())
                })
            }
        }
    }
}

mononoke_queries! {
    write AddRenames(values: (
        repo_id: RepositoryId,
        dst_cs_id: ChangesetId,
        dst_path_hash: Vec<u8>,
        src_cs_id: ChangesetId,
        src_path_hash: Vec<u8>,
        src_unode_id: Blake2,
        is_tree: i8,
    )) {
        none,
        mysql(
            "INSERT INTO mutable_renames (repo_id, dst_cs_id, dst_path_hash, src_cs_id, src_path_hash, src_unode_id, is_tree) VALUES {values}
            ON DUPLICATE KEY UPDATE src_cs_id = VALUES(src_cs_id), src_path_hash = VALUES(src_path_hash), src_unode_id = VALUES(src_unode_id), is_tree = VALUES(is_tree)
            "
        )
        sqlite(
            "REPLACE INTO mutable_renames (repo_id, dst_cs_id, dst_path_hash, src_cs_id, src_path_hash, src_unode_id, is_tree) VALUES {values}"
        )
    }

    write AddPaths(values: (
        path_hash: Vec<u8>,
        path: Vec<u8>,
    )) {
        insert_or_ignore,
        "{insert_or_ignore} INTO mutable_renames_paths (path_hash, path) VALUES {values}"
    }

    read GetRename(repo_id: RepositoryId, dst_cs_id: ChangesetId, dst_path_hash: Vec<u8>) -> (
       ChangesetId,
       Vec<u8>,
       Blake2,
       i8
    ) {
        "
        SELECT
            mutable_renames.src_cs_id,
            mutable_renames_paths.path,
            mutable_renames.src_unode_id,
            mutable_renames.is_tree
        FROM mutable_renames JOIN mutable_renames_paths
        ON mutable_renames.src_path_hash = mutable_renames_paths.path_hash
        WHERE mutable_renames.repo_id = {repo_id}
           AND mutable_renames.dst_cs_id = {dst_cs_id}
           AND  mutable_renames.dst_path_hash = {dst_path_hash}
        "
    }

    read HasRenameCheck(repo_id: RepositoryId, dst_cs_id: ChangesetId) -> (ChangesetId) {
        "
        SELECT
            mutable_renames.src_cs_id
        FROM mutable_renames
        WHERE
            mutable_renames.repo_id = {repo_id}
           AND mutable_renames.dst_cs_id = {dst_cs_id}
        LIMIT 1
        "
    }

    read FindRenames(repo_id: RepositoryId, dst_path_hash: Vec<u8>) -> (ChangesetId) {
        "
        SELECT
            mutable_renames.dst_cs_id
        FROM mutable_renames
        WHERE
            mutable_renames.repo_id = {repo_id}
            AND mutable_renames.dst_path_hash = {dst_path_hash}
        "
    }
}